Commit 3a5d3c8f authored by Steven's avatar Steven

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.
parent 3e4c052f
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<MemoViewProps> = (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<MemoViewProps> = (props: MemoViewProps) => {
);
}
return (
<MemoViewContext.Provider value={contextValue}>
<article className={cn(MEMO_CARD_BASE_CLASSES, className)} ref={cardRef} tabIndex={readonly ? -1 : 0}>
<MemoHeader
showCreator={props.showCreator}
showVisibility={props.showVisibility}
showPinned={props.showPinned}
onEdit={openEditor}
onGotoDetail={handleGotoMemoDetailPage}
onUnpin={unpinMemo}
/>
const article = (
<article
className={cn(MEMO_CARD_BASE_CLASSES, showCommentPreview ? "mb-0 rounded-b-none" : "mb-2", className)}
ref={cardRef}
tabIndex={readonly ? -1 : 0}
>
<MemoHeader
showCreator={props.showCreator}
showVisibility={props.showVisibility}
showPinned={props.showPinned}
onEdit={openEditor}
onGotoDetail={handleGotoMemoDetailPage}
onUnpin={unpinMemo}
/>
<MemoBody
compact={props.compact}
onContentClick={handleMemoContentClick}
onContentDoubleClick={handleMemoContentDoubleClick}
onToggleNsfwVisibility={toggleNsfwVisibility}
/>
<MemoBody
compact={props.compact}
onContentClick={handleMemoContentClick}
onContentDoubleClick={handleMemoContentDoubleClick}
onToggleNsfwVisibility={toggleNsfwVisibility}
/>
<PreviewImageDialog
open={previewState.open}
onOpenChange={setPreviewOpen}
imgUrls={previewState.urls}
initialIndex={previewState.index}
/>
</article>
);
<PreviewImageDialog
open={previewState.open}
onOpenChange={setPreviewOpen}
imgUrls={previewState.urls}
initialIndex={previewState.index}
/>
</article>
return (
<MemoViewContext.Provider value={contextValue}>
{showCommentPreview ? (
<div className="mb-2">
{article}
<MemoCommentListView />
</div>
) : (
article
)}
</MemoViewContext.Provider>
);
};
......
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 (
<div className="border border-t-0 border-border rounded-b-xl px-4 pt-2 pb-3 flex flex-col gap-1">
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-muted-foreground">Comments{commentAmount > 1 ? ` (${commentAmount})` : ""}</span>
<Link
to={`/${memo.name}#comments`}
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground hover:underline underline-offset-2 transition-colors"
>
View all
<ArrowUpRightIcon className="w-3 h-3" />
</Link>
</div>
{displayedComments.map((comment) => (
<div key={comment.name} className="bg-muted/60 rounded-md px-2 py-1 text-xs text-muted-foreground truncate leading-relaxed">
{comment.content}
</div>
))}
</div>
);
};
export default MemoCommentListView;
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<MemoHeaderProps> = ({ 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<MemoHeaderProps> = ({ showCreator, showVisibility, sh
/>
)}
{!isInMemoDetailPage && commentAmount > 0 && (
<Link
className={cn("flex flex-row justify-start items-center rounded-md px-1 hover:opacity-80 gap-0.5")}
to={`/${memo.name}#comments`}
viewTransition
state={{ from: parentPage }}
>
<MessageCircleMoreIcon className="w-4 h-4 mx-auto text-muted-foreground" />
<span className="text-xs text-muted-foreground">{commentAmount}</span>
</Link>
)}
{showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip>
<TooltipTrigger>
......
export { default as MemoBody } from "./MemoBody";
export { default as MemoCommentListView } from "./MemoCommentListView";
export { default as MemoHeader } from "./MemoHeader";
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment