From 3a5d3c8ff92ae5c24559dc4574300d6475820a87 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 3 Mar 2026 21:40:56 +0800 Subject: [PATCH] feat: show inline comment preview in list view Add a comment preview section below memo cards in list view, displaying up to 3 comment snippets with a "View all" link. Removes the old comment count icon from the memo header in favor of this richer inline display. Comment preview is hidden in memo detail view. --- web/src/components/MemoView/MemoView.tsx | 74 ++++++++++++------- .../components/MemoCommentListView.tsx | 40 ++++++++++ .../MemoView/components/MemoHeader.tsx | 18 +---- .../components/MemoView/components/index.ts | 1 + 4 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 web/src/components/MemoView/components/MemoCommentListView.tsx diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx index f59a3f676..5986d3206 100644 --- a/web/src/components/MemoView/MemoView.tsx +++ b/web/src/components/MemoView/MemoView.tsx @@ -1,12 +1,14 @@ import { memo, useMemo, useRef, useState } from "react"; +import { useLocation } from "react-router-dom"; import useCurrentUser from "@/hooks/useCurrentUser"; import { useUser } from "@/hooks/useUserQueries"; import { cn } from "@/lib/utils"; import { State } from "@/types/proto/api/v1/common_pb"; +import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import { isSuperUser } from "@/utils/user"; import MemoEditor from "../MemoEditor"; import PreviewImageDialog from "../PreviewImageDialog"; -import { MemoBody, MemoHeader } from "./components"; +import { MemoBody, MemoCommentListView, MemoHeader } from "./components"; import { MEMO_CARD_BASE_CLASSES } from "./constants"; import { useImagePreview, useMemoActions, useMemoHandlers } from "./hooks"; import { MemoViewContext } from "./MemoViewContext"; @@ -42,6 +44,13 @@ const MemoView: React.FC = (props: MemoViewProps) => { openPreview, }); + const location = useLocation(); + const isInMemoDetailPage = location.pathname.startsWith(`/${memoData.name}`); + const commentAmount = memoData.relations.filter( + (r) => r.type === MemoRelation_Type.COMMENT && r.relatedMemo?.name === memoData.name, + ).length; + const showCommentPreview = !isInMemoDetailPage && commentAmount > 0; + const contextValue = useMemo( () => ({ memo: memoData, @@ -69,32 +78,47 @@ const MemoView: React.FC = (props: MemoViewProps) => { ); } + const article = ( +
+ + + + + +
+ ); + return ( -
- - - - - -
+ {showCommentPreview ? ( +
+ {article} + +
+ ) : ( + article + )}
); }; diff --git a/web/src/components/MemoView/components/MemoCommentListView.tsx b/web/src/components/MemoView/components/MemoCommentListView.tsx new file mode 100644 index 000000000..e62714f01 --- /dev/null +++ b/web/src/components/MemoView/components/MemoCommentListView.tsx @@ -0,0 +1,40 @@ +import { ArrowUpRightIcon } from "lucide-react"; +import { Link } from "react-router-dom"; +import { useMemoComments } from "@/hooks/useMemoQueries"; +import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext"; + +const MemoCommentListView: React.FC = () => { + const { memo } = useMemoViewContext(); + const { isInMemoDetailPage, commentAmount } = useMemoViewDerived(); + + const { data } = useMemoComments(memo.name, { enabled: !isInMemoDetailPage && commentAmount > 0 }); + const comments = data?.memos ?? []; + + if (isInMemoDetailPage || commentAmount === 0) { + return null; + } + + const displayedComments = comments.slice(0, 3); + + return ( +
+
+ Comments{commentAmount > 1 ? ` (${commentAmount})` : ""} + + View all + + +
+ {displayedComments.map((comment) => ( +
+ {comment.content} +
+ ))} +
+ ); +}; + +export default MemoCommentListView; diff --git a/web/src/components/MemoView/components/MemoHeader.tsx b/web/src/components/MemoView/components/MemoHeader.tsx index 7a58d0e1e..375e578d4 100644 --- a/web/src/components/MemoView/components/MemoHeader.tsx +++ b/web/src/components/MemoView/components/MemoHeader.tsx @@ -1,5 +1,5 @@ import { timestampDate } from "@bufbuild/protobuf/wkt"; -import { BookmarkIcon, MessageCircleMoreIcon } from "lucide-react"; +import { BookmarkIcon } from "lucide-react"; import { useState } from "react"; import { Link } from "react-router-dom"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; @@ -20,8 +20,8 @@ const MemoHeader: React.FC = ({ showCreator, showVisibility, sh const t = useTranslate(); const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false); - const { memo, creator, currentUser, parentPage, isArchived, readonly } = useMemoViewContext(); - const { isInMemoDetailPage, commentAmount, relativeTimeFormat } = useMemoViewDerived(); + const { memo, creator, currentUser, isArchived, readonly } = useMemoViewContext(); + const { relativeTimeFormat } = useMemoViewDerived(); const displayTime = isArchived ? ( (memo.displayTime ? timestampDate(memo.displayTime) : undefined)?.toLocaleString(i18n.language) @@ -52,18 +52,6 @@ const MemoHeader: React.FC = ({ showCreator, showVisibility, sh /> )} - {!isInMemoDetailPage && commentAmount > 0 && ( - - - {commentAmount} - - )} - {showVisibility && memo.visibility !== Visibility.PRIVATE && ( diff --git a/web/src/components/MemoView/components/index.ts b/web/src/components/MemoView/components/index.ts index 7e36dd8a1..a2baa27bf 100644 --- a/web/src/components/MemoView/components/index.ts +++ b/web/src/components/MemoView/components/index.ts @@ -1,2 +1,3 @@ export { default as MemoBody } from "./MemoBody"; +export { default as MemoCommentListView } from "./MemoCommentListView"; export { default as MemoHeader } from "./MemoHeader";