From f0b170184f8a034764048db822e7d3e352de0e10 Mon Sep 17 00:00:00 2001 From: jindongfu Date: Mon, 29 Jun 2026 16:30:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20pinned=20chip=20=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=20+=20=E5=B7=A6=E9=94=AE=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bug: 1) Pinned 区域的 chip 只显示 ref_id (例如 'ref_88c117'), _findRefLabelFromThread 是早期 stub,从未真正去 ref index 里查 label。 2) Pinned chip 渲染成
(非 ),通用 file-chip 点击拦截器只匹配 .file-chip-ref / .file-chip-missing / .file-chip,所以左键完全没反应。 fix: - _findRefLabelFromThread 改为查 window._forgeRefs.byId(loadRefIndex 已 缓存),拿真实 label;renderPinnedArea 渲染前 await loadRefIndex() 兜底。 - 全局 click 拦截器里加 .pinned-chip 分支:忽略 × 关闭按钮,其余左键 -> 跳 #/files/refs/,复用既有 routeFromHash 路径。 - chip title hover 也用 label 而非 ref_id。 backend tests (test_server_thread_pin / test_thread_pin) 绿。 --- web/app.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/web/app.js b/web/app.js index 2a75b1d..b8df43f 100644 --- a/web/app.js +++ b/web/app.js @@ -1405,17 +1405,12 @@ async function _resolveRefExistsBatch(ids) { } function _findRefLabelFromThread(refId) { - // Scan posts for [[ref:id]] tokens we've already resolved into chips — - // fall back to ref_id when unknown. - const t = state.currentThread; - if (!t) return refId; - for (const p of t.posts || []) { - const re = new RegExp(`\\[\\[ref:${refId}\\]\\]`); - if (re.test(p.content || '')) { - // No reliable per-chip label here without re-resolving; reuse refId. - break; - } - } + // Prefer the ref index (window._forgeRefs) — it carries the real filename + // (label). Falls back to ref_id only when the index hasn't loaded yet or + // the ref is unknown. + const idx = window._forgeRefs; + const hit = idx && idx.byId && idx.byId.get(refId); + if (hit && hit.label) return hit.label; return refId; } @@ -1428,6 +1423,9 @@ async function renderPinnedArea(t) { root.hidden = true; return; } + // Ensure the ref index is loaded so we can show real filenames (labels) + // instead of opaque ref ids. Cheap if already cached. + try { await loadRefIndex(); } catch (_) { /* offline; fall back to ids */ } root.hidden = false; root.innerHTML = `
📌` + @@ -1439,7 +1437,7 @@ async function renderPinnedArea(t) { const by = escapeHtml(p.pinned_by || 'scott'); const when = p.pinned_at ? escapeHtml(formatRelative(p.pinned_at)) : ''; const stale = _refExistsCache.get(rid) === false; - return `
` + + return `
` + `📄` + `${label}` + `pinned by ${by}${when ? ' · ' + when : ''}` + @@ -1524,6 +1522,25 @@ document.addEventListener('click', async (e) => { // 让 ⭐ 收藏按钮、context menu 等先消化掉自己的事件。 if (e.defaultPrevented) return; if (e.target.closest('.file-chip-fav')) return; + // pinned chip has its own click semantics: the × (data-unpin) and the + // ⌥/contextmenu actions are handled by dedicated listeners; for plain + // left-click we want to route to the file preview, same as a regular + // file-chip-ref. + const pinned = e.target.closest('.pinned-chip'); + if (pinned) { + if (e.target.closest('.pinned-chip-close')) return; // let unpin handler run + if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; + const rid = pinned.getAttribute('data-ref-id'); + if (!rid) return; + e.preventDefault(); + const next = '#/files/refs/' + encodeURIComponent(rid); + if (location.hash === next) { + if (typeof window.__forgeRouteFromHash === 'function') window.__forgeRouteFromHash(); + } else { + location.hash = next; + } + return; + } if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; // 让 ⌘+click 等保留浏览器默认 const chip = e.target.closest('.file-chip-ref, .file-chip-missing, .file-chip'); if (!chip) return;