From ae98d33edf4e72b300d25b3f0362a821189f2225 Mon Sep 17 00:00:00 2001 From: Victor_Jay Date: Mon, 1 Jun 2026 17:58:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=94=B6=E8=97=8F?= =?UTF-8?q?=E5=A4=B9=E5=88=97=E8=A1=A8=E5=8A=A0=E8=BD=BD=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=20+=20=E6=94=B6=E8=97=8F=E6=8C=89=E9=92=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E9=80=89=E6=8B=A9=E6=94=B6=E8=97=8F=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - settings/favorites 收藏夹列表加载失败:escapeHTML 在 energy Tab 闭包内,收藏夹 Tab 无法访问导致 ReferenceError,修复为本地 esc() 函数 - settings 创建/删除收藏夹请求缺少 CSRF Token(403),统一使用 fetch + X-CSRF-Token 头 - API 错误时显示实际错误信息而非吞掉显示"暂无收藏夹" - 文章详情页收藏按钮:点击弹出选择收藏夹弹窗,列出所有收藏夹,已收藏的文章高亮显示 - 弹窗支持原地新建收藏夹并立即收藏(一键创建+收藏) - 新增收藏夹弹窗 CSS 样式(.fav-overlay/.fav-modal/.fav-folder-option 等) --- templates/MetaLab-2026/html/posts/show.html | 289 +++++++++++++++--- .../MetaLab-2026/html/settings/index.html | 62 ++-- templates/MetaLab-2026/static/css/posts.css | 178 +++++++++++ 3 files changed, 464 insertions(+), 65 deletions(-) diff --git a/templates/MetaLab-2026/html/posts/show.html b/templates/MetaLab-2026/html/posts/show.html index 668ac29..4ae8560 100644 --- a/templates/MetaLab-2026/html/posts/show.html +++ b/templates/MetaLab-2026/html/posts/show.html @@ -85,6 +85,25 @@ + + + {{end}} @@ -345,59 +364,253 @@ diff --git a/templates/MetaLab-2026/html/settings/index.html b/templates/MetaLab-2026/html/settings/index.html index 69fca77..e261153 100644 --- a/templates/MetaLab-2026/html/settings/index.html +++ b/templates/MetaLab-2026/html/settings/index.html @@ -1032,11 +1032,31 @@ var createBtn = document.getElementById('createFolderBtn'); if (!listEl) return; + var csrfMeta = document.querySelector('meta[name="csrf-token"]'); + var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : ''; + + function esc(s) { + var d = document.createElement('div'); + d.textContent = s || ''; + return d.innerHTML; + } + + function api(method, url, data) { + var opts = { method: method, headers: { 'Content-Type': 'application/json' } }; + if (csrfToken) opts.headers['X-CSRF-Token'] = csrfToken; + if (data) opts.body = JSON.stringify(data); + return fetch(url, opts).then(function (r) { return r.json(); }); + } + function loadFolders() { - fetch('/api/folders') - .then(function (r) { return r.json(); }) + listEl.innerHTML = '
加载中...
'; + api('GET', '/api/folders') .then(function (d) { - if (!d.success || !d.data || !d.data.folders || d.data.folders.length === 0) { + if (!d.success) { + listEl.innerHTML = '
' + esc(d.message || '加载失败') + '
'; + return; + } + if (!d.data || !d.data.folders || d.data.folders.length === 0) { listEl.innerHTML = '
暂无收藏夹
'; return; } @@ -1046,7 +1066,7 @@ var pub = f.is_public ? '公开' : '私密'; html += '
' + '
' - + '' + escapeHTML(f.name) + isDefault + '' + + '' + esc(f.name) + isDefault + '' + '' + (f.item_count || 0) + ' 篇文章 · ' + pub + '' + '
' + '
' @@ -1056,33 +1076,27 @@ }); listEl.innerHTML = html; - // 绑定删除按钮 listEl.querySelectorAll('.fav-folder-del').forEach(function (btn) { btn.addEventListener('click', function () { var id = btn.getAttribute('data-id'); if (!confirm('确定要删除此收藏夹?其中的收藏内容也将被清除。')) return; - var xhr = new XMLHttpRequest(); - xhr.open('DELETE', '/api/folders/' + id, true); - xhr.onreadystatechange = function () { - if (xhr.readyState !== 4) return; - try { - var r = JSON.parse(xhr.responseText); + api('DELETE', '/api/folders/' + id) + .then(function (r) { if (r.success) { showSettingsToast('收藏夹已删除'); loadFolders(); } else { showSettingsToast(r.message || '删除失败', true); } - } catch (e) { + }) + .catch(function () { showSettingsToast('删除失败', true); - } - }; - xhr.send(); + }); }); }); }) .catch(function () { - listEl.innerHTML = '
加载失败
'; + listEl.innerHTML = '
加载失败,请刷新页面重试
'; }); } @@ -1094,24 +1108,18 @@ showSettingsToast('收藏夹名称不能超过 50 个字符', true); return; } - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/folders', true); - xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.onreadystatechange = function () { - if (xhr.readyState !== 4) return; - try { - var r = JSON.parse(xhr.responseText); + api('POST', '/api/folders', { name: name.trim(), description: '', is_public: false }) + .then(function (r) { if (r.success) { showSettingsToast('收藏夹已创建'); loadFolders(); } else { showSettingsToast(r.message || '创建失败', true); } - } catch (e) { + }) + .catch(function () { showSettingsToast('创建失败', true); - } - }; - xhr.send(JSON.stringify({ name: name.trim(), description: '', is_public: false })); + }); }); } diff --git a/templates/MetaLab-2026/static/css/posts.css b/templates/MetaLab-2026/static/css/posts.css index ba90377..042869e 100644 --- a/templates/MetaLab-2026/static/css/posts.css +++ b/templates/MetaLab-2026/static/css/posts.css @@ -790,6 +790,184 @@ font-weight: 500; } +/* ========== 选择收藏夹弹窗 ========== */ +.fav-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.fav-modal { + background: #fff; + border-radius: 12px; + width: 400px; + max-width: 92vw; + max-height: 80vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); + overflow: hidden; +} + +.fav-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px 16px; + border-bottom: 1px solid #f0f0f0; + flex-shrink: 0; +} + +.fav-modal-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #111827; +} + +.fav-close { + background: none; + border: none; + font-size: 24px; + color: #9ca3af; + cursor: pointer; + padding: 0; + line-height: 1; +} + +.fav-close:hover { + color: #374151; +} + +.fav-modal-body { + padding: 16px 24px; + overflow-y: auto; + flex: 1; +} + +.fav-loading { + text-align: center; + padding: 24px 0; + font-size: 14px; + color: #9ca3af; +} + +.fav-loading.fav-error { + color: #ef4444; +} + +.fav-empty { + text-align: center; + padding: 24px 0; + font-size: 14px; + color: #9ca3af; +} + +.fav-folder-list { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 16px; +} + +.fav-folder-option { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 14px; + border-radius: 8px; + border: 1px solid #e5e7eb; + cursor: pointer; + transition: all 0.15s ease; +} + +.fav-folder-option:hover { + border-color: #f59e0b; + background: #fffbeb; +} + +.fav-folder-option.active { + border-color: #f59e0b; + background: #fef3c7; + font-weight: 500; +} + +.fav-folder-option.active::after { + content: "✓"; + color: #f59e0b; + font-weight: 700; + font-size: 16px; +} + +.fav-folder-opt-name { + font-size: 14px; + color: #1f2937; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 8px; +} + +.fav-folder-opt-name small { + font-weight: 400; + color: #9ca3af; +} + +.fav-folder-opt-count { + font-size: 12px; + color: #9ca3af; + flex-shrink: 0; +} + +.fav-new-folder { + display: flex; + gap: 8px; + align-items: center; +} + +.fav-new-input { + flex: 1; + padding: 10px 14px; + border: 1px solid #d1d5db; + border-radius: 8px; + font-size: 14px; + font-family: inherit; + outline: none; + transition: border-color 0.15s; +} + +.fav-new-input:focus { + border-color: #f59e0b; + box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.15); +} + +.fav-new-btn { + padding: 10px 16px; + border: none; + border-radius: 8px; + background: #f59e0b; + color: #fff; + font-size: 13px; + font-weight: 500; + font-family: inherit; + cursor: pointer; + transition: background 0.15s; + white-space: nowrap; +} + +.fav-new-btn:hover { + background: #d97706; +} + +.fav-new-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + /* ========== 评论区 ========== */ .comments-section { max-width: 800px;