From a6e8ba7fb29505f6f91167c6c3d69f9835cb5de8 Mon Sep 17 00:00:00 2001 From: Johnny Date: Sat, 3 Jan 2026 12:49:13 +0800 Subject: [PATCH] refactor: consolidate MemoEditor components (#5409) --- ...achmentItemCard.tsx => AttachmentList.tsx} | 105 ++++++++++++---- .../components/AttachmentListV2.tsx | 81 ------------ .../MemoEditor/components/EditorMetadata.tsx | 15 +-- ...ationDisplayV2.tsx => LocationDisplay.tsx} | 6 +- .../components/RelationItemCard.tsx | 64 ---------- .../MemoEditor/components/RelationList.tsx | 117 ++++++++++++++++++ .../MemoEditor/components/RelationListV2.tsx | 62 ---------- .../components/MemoEditor/components/index.ts | 8 +- 8 files changed, 211 insertions(+), 247 deletions(-) rename web/src/components/MemoEditor/components/{AttachmentItemCard.tsx => AttachmentList.tsx} (50%) delete mode 100644 web/src/components/MemoEditor/components/AttachmentListV2.tsx rename web/src/components/MemoEditor/components/{LocationDisplayV2.tsx => LocationDisplay.tsx} (90%) delete mode 100644 web/src/components/MemoEditor/components/RelationItemCard.tsx create mode 100644 web/src/components/MemoEditor/components/RelationList.tsx delete mode 100644 web/src/components/MemoEditor/components/RelationListV2.tsx diff --git a/web/src/components/MemoEditor/components/AttachmentItemCard.tsx b/web/src/components/MemoEditor/components/AttachmentList.tsx similarity index 50% rename from web/src/components/MemoEditor/components/AttachmentItemCard.tsx rename to web/src/components/MemoEditor/components/AttachmentList.tsx index 3af741db4..55f7a4666 100644 --- a/web/src/components/MemoEditor/components/AttachmentItemCard.tsx +++ b/web/src/components/MemoEditor/components/AttachmentList.tsx @@ -1,39 +1,32 @@ -import { ChevronDownIcon, ChevronUpIcon, FileIcon, Loader2Icon, XIcon } from "lucide-react"; +import { ChevronDownIcon, ChevronUpIcon, FileIcon, Loader2Icon, PaperclipIcon, XIcon } from "lucide-react"; import type { FC } from "react"; -import type { AttachmentItem } from "@/components/memo-metadata/types"; +import type { LocalFile } from "@/components/memo-metadata/types"; +import { toAttachmentItems } from "@/components/memo-metadata/types"; import { cn } from "@/lib/utils"; +import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { formatFileSize, getFileTypeLabel } from "@/utils/format"; -interface AttachmentItemCardProps { - item: AttachmentItem; +interface AttachmentListProps { + attachments: Attachment[]; + localFiles?: LocalFile[]; + onAttachmentsChange?: (attachments: Attachment[]) => void; + onRemoveLocalFile?: (previewUrl: string) => void; +} + +const AttachmentItemCard: FC<{ + item: ReturnType[0]; onRemove?: () => void; onMoveUp?: () => void; onMoveDown?: () => void; canMoveUp?: boolean; canMoveDown?: boolean; - className?: string; -} - -const AttachmentItemCard: FC = ({ - item, - onRemove, - onMoveUp, - onMoveDown, - canMoveUp = true, - canMoveDown = true, - className, -}) => { +}> = ({ item, onRemove, onMoveUp, onMoveDown, canMoveUp = true, canMoveDown = true }) => { const { category, filename, thumbnailUrl, mimeType, size, isLocal } = item; const fileTypeLabel = getFileTypeLabel(mimeType); const fileSizeLabel = size ? formatFileSize(size) : undefined; return ( -
+
{category === "image" && thumbnailUrl ? ( @@ -113,4 +106,70 @@ const AttachmentItemCard: FC = ({ ); }; -export default AttachmentItemCard; +const AttachmentList: FC = ({ attachments, localFiles = [], onAttachmentsChange, onRemoveLocalFile }) => { + if (attachments.length === 0 && localFiles.length === 0) { + return null; + } + + const items = toAttachmentItems(attachments, localFiles); + + const handleMoveUp = (index: number) => { + if (index === 0 || !onAttachmentsChange) return; + + const newAttachments = [...attachments]; + [newAttachments[index - 1], newAttachments[index]] = [newAttachments[index], newAttachments[index - 1]]; + onAttachmentsChange(newAttachments); + }; + + const handleMoveDown = (index: number) => { + if (index === attachments.length - 1 || !onAttachmentsChange) return; + + const newAttachments = [...attachments]; + [newAttachments[index], newAttachments[index + 1]] = [newAttachments[index + 1], newAttachments[index]]; + onAttachmentsChange(newAttachments); + }; + + const handleRemoveAttachment = (name: string) => { + if (onAttachmentsChange) { + onAttachmentsChange(attachments.filter((attachment) => attachment.name !== name)); + } + }; + + const handleRemoveItem = (item: (typeof items)[0]) => { + if (item.isLocal) { + onRemoveLocalFile?.(item.id); + } else { + handleRemoveAttachment(item.id); + } + }; + + return ( +
+
+ + Attachments ({items.length}) +
+ +
+ {items.map((item) => { + const isLocalFile = item.isLocal; + const attachmentIndex = isLocalFile ? -1 : attachments.findIndex((a) => a.name === item.id); + + return ( + handleRemoveItem(item)} + onMoveUp={!isLocalFile ? () => handleMoveUp(attachmentIndex) : undefined} + onMoveDown={!isLocalFile ? () => handleMoveDown(attachmentIndex) : undefined} + canMoveUp={!isLocalFile && attachmentIndex > 0} + canMoveDown={!isLocalFile && attachmentIndex < attachments.length - 1} + /> + ); + })} +
+
+ ); +}; + +export default AttachmentList; diff --git a/web/src/components/MemoEditor/components/AttachmentListV2.tsx b/web/src/components/MemoEditor/components/AttachmentListV2.tsx deleted file mode 100644 index 369a808a5..000000000 --- a/web/src/components/MemoEditor/components/AttachmentListV2.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { PaperclipIcon } from "lucide-react"; -import type { FC } from "react"; -import type { LocalFile } from "@/components/memo-metadata/types"; -import { toAttachmentItems } from "@/components/memo-metadata/types"; -import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; -import AttachmentItemCard from "./AttachmentItemCard"; - -interface AttachmentListV2Props { - attachments: Attachment[]; - localFiles?: LocalFile[]; - onAttachmentsChange?: (attachments: Attachment[]) => void; - onRemoveLocalFile?: (previewUrl: string) => void; -} - -const AttachmentListV2: FC = ({ attachments, localFiles = [], onAttachmentsChange, onRemoveLocalFile }) => { - if (attachments.length === 0 && localFiles.length === 0) { - return null; - } - - const items = toAttachmentItems(attachments, localFiles); - - const handleMoveUp = (index: number) => { - if (index === 0 || !onAttachmentsChange) return; - - const newAttachments = [...attachments]; - [newAttachments[index - 1], newAttachments[index]] = [newAttachments[index], newAttachments[index - 1]]; - onAttachmentsChange(newAttachments); - }; - - const handleMoveDown = (index: number) => { - if (index === attachments.length - 1 || !onAttachmentsChange) return; - - const newAttachments = [...attachments]; - [newAttachments[index], newAttachments[index + 1]] = [newAttachments[index + 1], newAttachments[index]]; - onAttachmentsChange(newAttachments); - }; - - const handleRemoveAttachment = (name: string) => { - if (onAttachmentsChange) { - onAttachmentsChange(attachments.filter((attachment) => attachment.name !== name)); - } - }; - - const handleRemoveItem = (item: (typeof items)[0]) => { - if (item.isLocal) { - onRemoveLocalFile?.(item.id); - } else { - handleRemoveAttachment(item.id); - } - }; - - return ( -
-
- - Attachments ({items.length}) -
- -
- {items.map((item) => { - const isLocalFile = item.isLocal; - const attachmentIndex = isLocalFile ? -1 : attachments.findIndex((a) => a.name === item.id); - - return ( - handleRemoveItem(item)} - onMoveUp={!isLocalFile ? () => handleMoveUp(attachmentIndex) : undefined} - onMoveDown={!isLocalFile ? () => handleMoveDown(attachmentIndex) : undefined} - canMoveUp={!isLocalFile && attachmentIndex > 0} - canMoveDown={!isLocalFile && attachmentIndex < attachments.length - 1} - /> - ); - })} -
-
- ); -}; - -export default AttachmentListV2; diff --git a/web/src/components/MemoEditor/components/EditorMetadata.tsx b/web/src/components/MemoEditor/components/EditorMetadata.tsx index bc0fcba4f..dc9ba17bd 100644 --- a/web/src/components/MemoEditor/components/EditorMetadata.tsx +++ b/web/src/components/MemoEditor/components/EditorMetadata.tsx @@ -1,29 +1,26 @@ import type { FC } from "react"; import { useEditorContext } from "../state"; import type { EditorMetadataProps } from "../types"; -import AttachmentListV2 from "./AttachmentListV2"; -import LocationDisplayV2 from "./LocationDisplayV2"; -import RelationListV2 from "./RelationListV2"; +import AttachmentList from "./AttachmentList"; +import LocationDisplay from "./LocationDisplay"; +import RelationList from "./RelationList"; export const EditorMetadata: FC = () => { const { state, actions, dispatch } = useEditorContext(); return (
- dispatch(actions.setMetadata({ attachments }))} onRemoveLocalFile={(previewUrl) => dispatch(actions.removeLocalFile(previewUrl))} /> - dispatch(actions.setMetadata({ relations }))} - /> + dispatch(actions.setMetadata({ relations }))} /> {state.metadata.location && ( - dispatch(actions.setMetadata({ location: undefined }))} /> + dispatch(actions.setMetadata({ location: undefined }))} /> )}
); diff --git a/web/src/components/MemoEditor/components/LocationDisplayV2.tsx b/web/src/components/MemoEditor/components/LocationDisplay.tsx similarity index 90% rename from web/src/components/MemoEditor/components/LocationDisplayV2.tsx rename to web/src/components/MemoEditor/components/LocationDisplay.tsx index eccbaf742..53b5a6e68 100644 --- a/web/src/components/MemoEditor/components/LocationDisplayV2.tsx +++ b/web/src/components/MemoEditor/components/LocationDisplay.tsx @@ -3,13 +3,13 @@ import type { FC } from "react"; import { cn } from "@/lib/utils"; import type { Location } from "@/types/proto/api/v1/memo_service_pb"; -interface LocationDisplayV2Props { +interface LocationDisplayProps { location: Location; onRemove?: () => void; className?: string; } -const LocationDisplayV2: FC = ({ location, onRemove, className }) => { +const LocationDisplay: FC = ({ location, onRemove, className }) => { const displayText = location.placeholder || `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`; return ( @@ -45,4 +45,4 @@ const LocationDisplayV2: FC = ({ location, onRemove, cla ); }; -export default LocationDisplayV2; +export default LocationDisplay; diff --git a/web/src/components/MemoEditor/components/RelationItemCard.tsx b/web/src/components/MemoEditor/components/RelationItemCard.tsx deleted file mode 100644 index 9abdd40cd..000000000 --- a/web/src/components/MemoEditor/components/RelationItemCard.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { LinkIcon, XIcon } from "lucide-react"; -import type { FC } from "react"; -import { Link } from "react-router-dom"; -import { extractMemoIdFromName } from "@/helpers/resource-names"; -import { cn } from "@/lib/utils"; -import type { MemoRelation_Memo } from "@/types/proto/api/v1/memo_service_pb"; - -interface RelationItemCardProps { - memo: MemoRelation_Memo; - onRemove?: () => void; - parentPage?: string; - className?: string; -} - -const RelationItemCard: FC = ({ memo, onRemove, parentPage, className }) => { - const memoId = extractMemoIdFromName(memo.name); - - if (onRemove) { - return ( -
- - - {memo.snippet} - - -
- -
-
- ); - } - - return ( - - {memoId.slice(0, 6)} - - {memo.snippet} - - - ); -}; - -export default RelationItemCard; diff --git a/web/src/components/MemoEditor/components/RelationList.tsx b/web/src/components/MemoEditor/components/RelationList.tsx new file mode 100644 index 000000000..0d33bc8ff --- /dev/null +++ b/web/src/components/MemoEditor/components/RelationList.tsx @@ -0,0 +1,117 @@ +import { create } from "@bufbuild/protobuf"; +import { LinkIcon, XIcon } from "lucide-react"; +import type { FC } from "react"; +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { memoServiceClient } from "@/connect"; +import { extractMemoIdFromName } from "@/helpers/resource-names"; +import { cn } from "@/lib/utils"; +import type { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; +import { MemoRelation_MemoSchema } from "@/types/proto/api/v1/memo_service_pb"; + +interface RelationListProps { + relations: MemoRelation[]; + onRelationsChange?: (relations: MemoRelation[]) => void; + parentPage?: string; +} + +const RelationItemCard: FC<{ + memo: MemoRelation["relatedMemo"]; + onRemove?: () => void; + parentPage?: string; +}> = ({ memo, onRemove, parentPage }) => { + const memoId = extractMemoIdFromName(memo!.name); + + if (onRemove) { + return ( +
+ + + {memo!.snippet} + + +
+ +
+
+ ); + } + + return ( + + {memoId.slice(0, 6)} + + {memo!.snippet} + + + ); +}; + +const RelationList: FC = ({ relations, onRelationsChange, parentPage }) => { + const [referencingMemos, setReferencingMemos] = useState([]); + + useEffect(() => { + (async () => { + if (relations.length > 0) { + const requests = relations.map(async (relation) => { + return await memoServiceClient.getMemo({ name: relation.relatedMemo!.name }); + }); + const list = await Promise.all(requests); + setReferencingMemos(list); + } else { + setReferencingMemos([]); + } + })(); + }, [relations]); + + const handleDeleteRelation = (memoName: string) => { + if (onRelationsChange) { + onRelationsChange(relations.filter((relation) => relation.relatedMemo?.name !== memoName)); + } + }; + + if (referencingMemos.length === 0) { + return null; + } + + return ( +
+
+ + Relations ({referencingMemos.length}) +
+ +
+ {referencingMemos.map((memo) => ( + handleDeleteRelation(memo.name)} + parentPage={parentPage} + /> + ))} +
+
+ ); +}; + +export default RelationList; diff --git a/web/src/components/MemoEditor/components/RelationListV2.tsx b/web/src/components/MemoEditor/components/RelationListV2.tsx deleted file mode 100644 index cf9d13a03..000000000 --- a/web/src/components/MemoEditor/components/RelationListV2.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { create } from "@bufbuild/protobuf"; -import { LinkIcon } from "lucide-react"; -import type { FC } from "react"; -import { useEffect, useState } from "react"; -import { memoServiceClient } from "@/connect"; -import type { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; -import { MemoRelation_MemoSchema } from "@/types/proto/api/v1/memo_service_pb"; -import RelationItemCard from "./RelationItemCard"; - -interface RelationListV2Props { - relations: MemoRelation[]; - onRelationsChange?: (relations: MemoRelation[]) => void; -} - -const RelationListV2: FC = ({ relations, onRelationsChange }) => { - const [referencingMemos, setReferencingMemos] = useState([]); - - useEffect(() => { - (async () => { - if (relations.length > 0) { - const requests = relations.map(async (relation) => { - return await memoServiceClient.getMemo({ name: relation.relatedMemo!.name }); - }); - const list = await Promise.all(requests); - setReferencingMemos(list); - } else { - setReferencingMemos([]); - } - })(); - }, [relations]); - - const handleDeleteRelation = (memoName: string) => { - if (onRelationsChange) { - onRelationsChange(relations.filter((relation) => relation.relatedMemo?.name !== memoName)); - } - }; - - if (referencingMemos.length === 0) { - return null; - } - - return ( -
-
- - Relations ({referencingMemos.length}) -
- -
- {referencingMemos.map((memo) => ( - handleDeleteRelation(memo.name)} - /> - ))} -
-
- ); -}; - -export default RelationListV2; diff --git a/web/src/components/MemoEditor/components/index.ts b/web/src/components/MemoEditor/components/index.ts index be2d6f9e4..4a4ec4896 100644 --- a/web/src/components/MemoEditor/components/index.ts +++ b/web/src/components/MemoEditor/components/index.ts @@ -1,13 +1,11 @@ // UI components for MemoEditor -export { default as AttachmentItemCard } from "./AttachmentItemCard"; -export { default as AttachmentListV2 } from "./AttachmentListV2"; +export { default as AttachmentList } from "./AttachmentList"; export * from "./EditorContent"; export * from "./EditorMetadata"; export * from "./EditorToolbar"; export { FocusModeExitButton, FocusModeOverlay } from "./FocusModeOverlay"; export { LinkMemoDialog } from "./LinkMemoDialog"; export { LocationDialog } from "./LocationDialog"; -export { default as LocationDisplayV2 } from "./LocationDisplayV2"; -export { default as RelationItemCard } from "./RelationItemCard"; -export { default as RelationListV2 } from "./RelationListV2"; +export { default as LocationDisplay } from "./LocationDisplay"; +export { default as RelationList } from "./RelationList";