From 6f82299e90066bfa8ba059aa92082bc3f3313cbb Mon Sep 17 00:00:00 2001 From: zengjiayi819 Date: Fri, 23 Oct 2026 15:40:12 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(desktop):=20=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E7=B2=BE=E9=80=89=E5=8D=A1=E5=9B=BA=E5=AE=9A=E4=B8=89=E8=A1=8C?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=B6=85=E5=87=BA=E6=8A=98=E5=8F=A0=E3=80=81?= =?UTF-8?q?=E7=82=B9=E5=BC=80=E6=9F=A5=E7=9C=8B=E5=85=A8=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 精选卡内容区预留三行,超出行数用 line-clamp 折叠 - 真被截断时才显示「展开/收起」按钮(aria-expanded 同步) - 展开时卡片自然变高;折叠态就是最小三行状态 - zh-CN/en-US 文案 timeline.expandFeatured / timeline.collapseFeatured - 新增 tests/memory-featured-expand.test.tsx(2 条,测溢出探测与展开切换) --- .../pages/memory/AtomicMemoryPane.tsx | 41 ++++++- .../components/pages/memory/MemoryPage.css | 60 ++++++++++- .../src/i18n/locales/en-US/memory.json | 2 + .../src/i18n/locales/zh-CN/memory.json | 2 + .../tests/memory-featured-expand.test.tsx | 100 ++++++++++++++++++ 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 apps/desktop/tests/memory-featured-expand.test.tsx diff --git a/apps/desktop/src/renderer/src/components/pages/memory/AtomicMemoryPane.tsx b/apps/desktop/src/renderer/src/components/pages/memory/AtomicMemoryPane.tsx index 71aefcc6..00a59376 100644 --- a/apps/desktop/src/renderer/src/components/pages/memory/AtomicMemoryPane.tsx +++ b/apps/desktop/src/renderer/src/components/pages/memory/AtomicMemoryPane.tsx @@ -312,6 +312,10 @@ export function AtomicMemoryPane({ focusItemId, onOpenDocument, onOpenConversati const [expandedId, setExpandedId] = useState(null) const [reloadTick, setReloadTick] = useState(0) const [featuredDismissed, setFeaturedDismissed] = useState(false) + // 精选正文固定占三行:超出三行折叠,点「展开」看全文(收起后回到三行状态)。 + const [featuredExpanded, setFeaturedExpanded] = useState(false) + const [featuredClamped, setFeaturedClamped] = useState(false) + const featuredContentRef = useRef(null) // 动态类型 chips 从「累计见过的类型」推导而非当前 items:items 会被类型过滤收窄, // 否则一切换筛选,其余动态类型标签会跟着消失,看起来像标签栏自动收起。 const [knownTypes, setKnownTypes] = useState([]) @@ -388,6 +392,25 @@ export function AtomicMemoryPane({ focusItemId, onOpenDocument, onOpenConversati })[0] }, [filtered, items]) + // 换一条精选(或收起)时回到三行态。 + useEffect(() => { + setFeaturedExpanded(false) + }, [featured?.id]) + + // 只有真被截断时才给「展开」:量一次 scrollHeight vs clientHeight, + // 窗口宽度变化会改变折行,所以跟随 resize 复测。 + useEffect(() => { + if (!featured || featuredExpanded) return + const measure = () => { + const element = featuredContentRef.current + if (!element || typeof element.scrollHeight !== 'number') return + setFeaturedClamped(element.scrollHeight > element.clientHeight + 1) + } + measure() + window.addEventListener?.('resize', measure) + return () => window.removeEventListener?.('resize', measure) + }, [featured?.id, featured?.content, featuredExpanded]) + const grouped = useMemo(() => { const groups: Array<{ id: TimelineBucketId; label: string; items: MemoryAtomicItemDto[] }> = [] for (const bucket of TIMELINE_BUCKETS) { @@ -423,9 +446,25 @@ export function AtomicMemoryPane({ focusItemId, onOpenDocument, onOpenConversati {featured.roomTitle ? 仅 Room: {featured.roomTitle} : 全局} -

{featured.content}

+

+ {featured.content} +

{formatDate(featured.updatedAt, locale)} + {featuredClamped || featuredExpanded ? ( + + ) : null}