mirror of https://github.com/usememos/memos.git
feat(memo-preview): support comment metadata in previews (#5768)
Co-authored-by: memoclaw <265580040+memoclaw@users.noreply.github.com>
This commit is contained in:
parent
22519b57a0
commit
e176b28c80
|
|
@ -4,7 +4,6 @@ import { MemoPreview } from "@/components/MemoPreview";
|
|||
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { VisuallyHidden } from "@/components/ui/visually-hidden";
|
||||
import { extractMemoIdFromName } from "@/helpers/resource-names";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
|
@ -75,12 +74,9 @@ export const LinkMemoDialog = ({
|
|||
<div className="w-full flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1.5 text-sm text-muted-foreground select-none">
|
||||
{alreadyLinked && <LinkIcon className="w-3 h-3 shrink-0" />}
|
||||
<span className="text-xs font-mono px-1 py-0.5 rounded border border-border bg-muted/40 shrink-0">
|
||||
{extractMemoIdFromName(memo.name).slice(0, 6)}
|
||||
</span>
|
||||
<span>{memo.displayTime && timestampDate(memo.displayTime).toLocaleString()}</span>
|
||||
</div>
|
||||
<MemoPreview content={memo.content} attachments={memo.attachments} />
|
||||
<MemoPreview name={memo.name} content={memo.content} attachments={memo.attachments} showMemoId />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { create } from "@bufbuild/protobuf";
|
||||
import { FileIcon } from "lucide-react";
|
||||
import { extractMemoIdFromName } from "@/helpers/resource-names";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
|
||||
import { MemoSchema } from "@/types/proto/api/v1/memo_service_pb";
|
||||
import type { User } from "@/types/proto/api/v1/user_service_pb";
|
||||
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
|
||||
import MemoContent from "../MemoContent";
|
||||
import { MemoViewContext, type MemoViewContextValue } from "../MemoView/MemoViewContext";
|
||||
|
|
@ -10,8 +12,13 @@ import { MemoViewContext, type MemoViewContextValue } from "../MemoView/MemoView
|
|||
interface MemoPreviewProps {
|
||||
content: string;
|
||||
attachments: Attachment[];
|
||||
name?: string;
|
||||
compact?: boolean;
|
||||
className?: string;
|
||||
creator?: User;
|
||||
showCreator?: boolean;
|
||||
showMemoId?: boolean;
|
||||
truncate?: boolean;
|
||||
}
|
||||
|
||||
const STUB_CONTEXT: MemoViewContextValue = {
|
||||
|
|
@ -57,18 +64,76 @@ const AttachmentThumbnails = ({ attachments }: { attachments: Attachment[] }) =>
|
|||
);
|
||||
};
|
||||
|
||||
const MemoPreview = ({ content, attachments, compact = true, className }: MemoPreviewProps) => {
|
||||
const PreviewMeta = ({
|
||||
creator,
|
||||
showCreator,
|
||||
memoName,
|
||||
showMemoId,
|
||||
}: {
|
||||
creator?: User;
|
||||
showCreator?: boolean;
|
||||
memoName?: string;
|
||||
showMemoId?: boolean;
|
||||
}) => {
|
||||
const creatorName = creator?.displayName || creator?.username;
|
||||
const memoId = showMemoId && memoName ? extractMemoIdFromName(memoName).slice(0, 6) : undefined;
|
||||
|
||||
if (!creatorName && !memoId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground leading-none shrink-0">
|
||||
{showMemoId && memoId && (
|
||||
<span className="text-[8px] font-mono px-1 py-0.5 rounded border border-border bg-muted/40 shrink-0">{memoId}</span>
|
||||
)}
|
||||
{showCreator && creatorName && <span className="font-medium text-foreground/80 truncate">{creatorName}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoPreview = ({
|
||||
content,
|
||||
attachments,
|
||||
name,
|
||||
compact = true,
|
||||
className,
|
||||
creator,
|
||||
showCreator = false,
|
||||
showMemoId = false,
|
||||
truncate = false,
|
||||
}: MemoPreviewProps) => {
|
||||
const hasContent = content.trim().length > 0;
|
||||
const hasAttachments = attachments.length > 0;
|
||||
const showMeta = showCreator || showMemoId;
|
||||
|
||||
if (!hasContent && !hasAttachments) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const meta = <PreviewMeta creator={creator} showCreator={showCreator} memoName={name} showMemoId={showMemoId} />;
|
||||
const contentNode = truncate ? (
|
||||
hasContent ? (
|
||||
<div className="text-sm text-muted-foreground truncate min-w-0">{content}</div>
|
||||
) : hasAttachments ? null : (
|
||||
<div className="text-sm text-muted-foreground truncate min-w-0">No content</div>
|
||||
)
|
||||
) : (
|
||||
hasContent && <MemoContent content={content} compact={compact} />
|
||||
);
|
||||
|
||||
return (
|
||||
<MemoViewContext.Provider value={STUB_CONTEXT}>
|
||||
<div className={cn("flex flex-col gap-1 pointer-events-none", className)}>
|
||||
{hasContent && <MemoContent content={content} compact={compact} />}
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none",
|
||||
truncate ? "flex items-center gap-1.5 min-w-0 leading-tight" : "flex flex-col gap-1",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{showMeta && meta}
|
||||
{showMeta && truncate && hasContent && <div className="text-muted-foreground/50 shrink-0">·</div>}
|
||||
{contentNode}
|
||||
{hasAttachments && <AttachmentThumbnails attachments={attachments} />}
|
||||
</div>
|
||||
</MemoViewContext.Provider>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { ArrowUpRightIcon } from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { MemoPreview } from "@/components/MemoPreview";
|
||||
import { extractMemoIdFromName } from "@/helpers/resource-names";
|
||||
import { useMemoComments } from "@/hooks/useMemoQueries";
|
||||
import { useUsersByNames } from "@/hooks/useUserQueries";
|
||||
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
|
||||
import MemoSnippetLink from "./MemoSnippetLink";
|
||||
|
||||
const MemoCommentListView: React.FC = () => {
|
||||
const { memo } = useMemoViewContext();
|
||||
|
|
@ -11,13 +12,13 @@ const MemoCommentListView: React.FC = () => {
|
|||
|
||||
const { data } = useMemoComments(memo.name, { enabled: !isInMemoDetailPage && commentAmount > 0 });
|
||||
const comments = data?.memos ?? [];
|
||||
const displayedComments = comments.slice(0, 3);
|
||||
const { data: commentCreators } = useUsersByNames(displayedComments.map((comment) => comment.creator));
|
||||
|
||||
if (isInMemoDetailPage || commentAmount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayedComments = comments.slice(0, 3);
|
||||
|
||||
return (
|
||||
<div className="border border-t-0 border-border rounded-b-lg px-4 pt-2 pb-3 flex flex-col gap-1">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
|
|
@ -32,14 +33,22 @@ const MemoCommentListView: React.FC = () => {
|
|||
</div>
|
||||
{displayedComments.map((comment) => {
|
||||
const uid = extractMemoIdFromName(comment.name);
|
||||
const creator = commentCreators?.get(comment.creator);
|
||||
return (
|
||||
<MemoSnippetLink
|
||||
<Link
|
||||
key={comment.name}
|
||||
name={comment.name}
|
||||
snippet={comment.snippet || comment.content}
|
||||
to={`/${memo.name}#${uid}`}
|
||||
className="bg-muted/40 rounded-md"
|
||||
/>
|
||||
viewTransition
|
||||
className="rounded-md bg-muted/40 px-2 py-1 transition-colors hover:bg-muted/60"
|
||||
>
|
||||
<MemoPreview
|
||||
content={comment.snippet || comment.content}
|
||||
attachments={comment.attachments}
|
||||
creator={creator}
|
||||
showCreator
|
||||
truncate
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue