diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 4b48d7401..e9868ca75 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -20,12 +20,10 @@ import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/t import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString } from "@/utils/memo"; import DateTimeInput from "../DateTimeInput"; +import { LocationDisplay, AttachmentList, RelationList } from "../memo-metadata"; import InsertMenu from "./ActionButton/InsertMenu"; import VisibilitySelector from "./ActionButton/VisibilitySelector"; -import AttachmentListView from "./AttachmentListView"; import Editor, { EditorRefActions } from "./Editor"; -import LocationView from "./LocationView"; -import RelationListView from "./RelationListView"; import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers"; import { MemoEditorContext } from "./types"; @@ -490,7 +488,8 @@ const MemoEditor = observer((props: Props) => { onCompositionEnd={handleCompositionEnd} > - setState((prevState) => ({ @@ -499,8 +498,8 @@ const MemoEditor = observer((props: Props) => { })) } /> - - + +
e.stopPropagation()}>
= observer((props: Props) => { compact={memo.pinned ? false : props.compact} // Always show full content when pinned. parentPage={parentPage} /> - {memo.location && } - - + {memo.location && } + +
{nsfw && !showNSFWContent && ( diff --git a/web/src/components/memo-metadata/AttachmentCard.tsx b/web/src/components/memo-metadata/AttachmentCard.tsx new file mode 100644 index 000000000..4b60f03ee --- /dev/null +++ b/web/src/components/memo-metadata/AttachmentCard.tsx @@ -0,0 +1,96 @@ +import { FileIcon, XIcon } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Attachment } from "@/types/proto/api/v1/attachment_service"; +import { getAttachmentThumbnailUrl, getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; +import { DisplayMode } from "./types"; + +interface AttachmentCardProps { + attachment: Attachment; + mode: DisplayMode; + onRemove?: () => void; + onClick?: () => void; + className?: string; + showThumbnail?: boolean; +} + +/** + * Shared attachment card component + * Displays thumbnails for images in both modes, with size variations + */ +const AttachmentCard = ({ attachment, mode, onRemove, onClick, className, showThumbnail = true }: AttachmentCardProps) => { + const type = getAttachmentType(attachment); + const attachmentUrl = getAttachmentUrl(attachment); + const attachmentThumbnailUrl = getAttachmentThumbnailUrl(attachment); + const isMedia = type === "image/*" || type === "video/*"; + + // Editor mode - compact badge style with thumbnail + if (mode === "edit") { + return ( +
+ {showThumbnail && type === "image/*" ? ( + {attachment.filename} + ) : ( + + )} + {attachment.filename} + {onRemove && ( + + )} +
+ ); + } + + // View mode - media gets special treatment + if (isMedia) { + if (type === "image/*") { + return ( + { + const target = e.target as HTMLImageElement; + if (target.src.includes("?thumbnail=true")) { + target.src = attachmentUrl; + } + }} + onClick={onClick} + decoding="async" + loading="lazy" + alt={attachment.filename} + /> + ); + } else if (type === "video/*") { + return ( +