From 4177898054838053942aa3cb6f82cc3b042ee9c4 Mon Sep 17 00:00:00 2001 From: memoclaw Date: Sat, 21 Mar 2026 09:04:15 +0800 Subject: [PATCH] refactor(web): consolidate memo metadata components into MemoMetadata (#5755) Co-authored-by: memoclaw <265580040+memoclaw@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- web/src/components/MemoAttachment.tsx | 36 ------------------- .../MemoEditor/Toolbar/InsertMenu.tsx | 2 +- .../MemoEditor/components/EditorMetadata.tsx | 10 +++--- .../components/MemoEditor/components/index.ts | 5 --- .../components/MemoEditor/types/components.ts | 24 ------------- web/src/components/MemoEditor/types/index.ts | 2 -- .../Attachment}/AttachmentCard.tsx | 0 .../Attachment/AttachmentListEditor.tsx} | 16 ++++----- .../Attachment/AttachmentListView.tsx} | 30 +++++++++------- .../MemoMetadata/Attachment/index.ts | 3 ++ .../Location}/LocationDialog.tsx | 14 +++++++- .../Location/LocationDisplayEditor.tsx} | 6 ++-- .../Location/LocationDisplayView.tsx} | 8 ++--- .../components/MemoMetadata/Location/index.ts | 3 ++ .../Relation}/LinkMemoDialog.tsx | 13 ++++++- .../Relation}/RelationCard.tsx | 2 +- .../Relation/RelationListEditor.tsx} | 21 ++++++----- .../Relation/RelationListView.tsx} | 8 ++--- .../components/MemoMetadata/Relation/index.ts | 4 +++ .../SectionHeader.tsx | 0 web/src/components/MemoMetadata/index.ts | 6 ++++ web/src/components/MemoResource.tsx | 0 .../MemoView/components/MemoBody.tsx | 8 ++--- .../MemoView/components/metadata/index.ts | 6 ---- web/src/pages/SharedMemo.tsx | 16 +++++++-- 25 files changed, 110 insertions(+), 133 deletions(-) delete mode 100644 web/src/components/MemoAttachment.tsx rename web/src/components/{MemoView/components/metadata => MemoMetadata/Attachment}/AttachmentCard.tsx (100%) rename web/src/components/{MemoEditor/components/AttachmentList.tsx => MemoMetadata/Attachment/AttachmentListEditor.tsx} (90%) rename web/src/components/{MemoView/components/metadata/AttachmentList.tsx => MemoMetadata/Attachment/AttachmentListView.tsx} (86%) create mode 100644 web/src/components/MemoMetadata/Attachment/index.ts rename web/src/components/{MemoEditor/components => MemoMetadata/Location}/LocationDialog.tsx (89%) rename web/src/components/{MemoEditor/components/LocationDisplay.tsx => MemoMetadata/Location/LocationDisplayEditor.tsx} (89%) rename web/src/components/{MemoView/components/metadata/LocationDisplay.tsx => MemoMetadata/Location/LocationDisplayView.tsx} (87%) create mode 100644 web/src/components/MemoMetadata/Location/index.ts rename web/src/components/{MemoEditor/components => MemoMetadata/Relation}/LinkMemoDialog.tsx (90%) rename web/src/components/{MemoView/components/metadata => MemoMetadata/Relation}/RelationCard.tsx (84%) rename web/src/components/{MemoEditor/components/RelationList.tsx => MemoMetadata/Relation/RelationListEditor.tsx} (80%) rename web/src/components/{MemoView/components/metadata/RelationList.tsx => MemoMetadata/Relation/RelationListView.tsx} (93%) create mode 100644 web/src/components/MemoMetadata/Relation/index.ts rename web/src/components/{MemoView/components/metadata => MemoMetadata}/SectionHeader.tsx (100%) create mode 100644 web/src/components/MemoMetadata/index.ts delete mode 100644 web/src/components/MemoResource.tsx delete mode 100644 web/src/components/MemoView/components/metadata/index.ts diff --git a/web/src/components/MemoAttachment.tsx b/web/src/components/MemoAttachment.tsx deleted file mode 100644 index 7f33ce69..00000000 --- a/web/src/components/MemoAttachment.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; -import { getAttachmentUrl, isMidiFile } from "@/utils/attachment"; -import AttachmentIcon from "./AttachmentIcon"; - -interface Props { - attachment: Attachment; - className?: string; -} - -const MemoAttachment: React.FC = (props: Props) => { - const { className, attachment } = props; - const attachmentUrl = getAttachmentUrl(attachment); - - const handlePreviewBtnClick = () => { - window.open(attachmentUrl); - }; - - return ( -
- {attachment.type.startsWith("audio") && !isMidiFile(attachment.type) ? ( - - ) : ( - <> - - - {attachment.filename} - - - )} -
- ); -}; - -export default MemoAttachment; diff --git a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx index d3e8dfa3..a3bd05fa 100644 --- a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx +++ b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx @@ -3,6 +3,7 @@ import { uniqBy } from "lodash-es"; import { FileIcon, LinkIcon, LoaderIcon, type LucideIcon, MapPinIcon, Maximize2Icon, MoreHorizontalIcon, PlusIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useDebounce } from "react-use"; +import { LinkMemoDialog, LocationDialog } from "@/components/MemoMetadata"; import { useReverseGeocoding } from "@/components/map"; import { Button } from "@/components/ui/button"; import { @@ -17,7 +18,6 @@ import { } from "@/components/ui/dropdown-menu"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; -import { LinkMemoDialog, LocationDialog } from "../components"; import { useFileUpload, useLinkMemo, useLocation } from "../hooks"; import { useEditorContext } from "../state"; import type { InsertMenuProps } from "../types"; diff --git a/web/src/components/MemoEditor/components/EditorMetadata.tsx b/web/src/components/MemoEditor/components/EditorMetadata.tsx index bee0bebf..df01ddf9 100644 --- a/web/src/components/MemoEditor/components/EditorMetadata.tsx +++ b/web/src/components/MemoEditor/components/EditorMetadata.tsx @@ -1,30 +1,28 @@ import type { FC } from "react"; +import { AttachmentListEditor, LocationDisplayEditor, RelationListEditor } from "@/components/MemoMetadata"; import { useEditorContext } from "../state"; import type { EditorMetadataProps } from "../types"; -import AttachmentList from "./AttachmentList"; -import LocationDisplay from "./LocationDisplay"; -import RelationList from "./RelationList"; export const EditorMetadata: FC = ({ memoName }) => { const { state, actions, dispatch } = useEditorContext(); return (
- dispatch(actions.setMetadata({ attachments }))} onRemoveLocalFile={(previewUrl) => dispatch(actions.removeLocalFile(previewUrl))} /> - dispatch(actions.setMetadata({ relations }))} memoName={memoName} /> {state.metadata.location && ( - dispatch(actions.setMetadata({ location: undefined }))} /> + dispatch(actions.setMetadata({ location: undefined }))} /> )}
); diff --git a/web/src/components/MemoEditor/components/index.ts b/web/src/components/MemoEditor/components/index.ts index 8949fbdc..26043d84 100644 --- a/web/src/components/MemoEditor/components/index.ts +++ b/web/src/components/MemoEditor/components/index.ts @@ -1,12 +1,7 @@ // UI components for MemoEditor -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 LocationDisplay } from "./LocationDisplay"; -export { default as RelationList } from "./RelationList"; export { TimestampPopover } from "./TimestampPopover"; diff --git a/web/src/components/MemoEditor/types/components.ts b/web/src/components/MemoEditor/types/components.ts index b53d7530..e99519d1 100644 --- a/web/src/components/MemoEditor/types/components.ts +++ b/web/src/components/MemoEditor/types/components.ts @@ -1,8 +1,6 @@ -import type { LatLng } from "leaflet"; import type { Location, Memo, Visibility } from "@/types/proto/api/v1/memo_service_pb"; import type { EditorRefActions } from "../Editor"; import type { Command } from "../Editor/commands"; -import type { LocationState } from "./insert-menu"; export interface MemoEditorProps { className?: string; @@ -41,28 +39,6 @@ export interface FocusModeExitButtonProps { title: string; } -export interface LinkMemoDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - searchText: string; - onSearchChange: (text: string) => void; - filteredMemos: Memo[]; - isFetching: boolean; - onSelectMemo: (memo: Memo) => void; - isAlreadyLinked: (memoName: string) => boolean; -} - -export interface LocationDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - state: LocationState; - onPositionChange: (position: LatLng) => void; - onUpdateCoordinate: (type: "lat" | "lng", value: string) => void; - onPlaceholderChange: (placeholder: string) => void; - onCancel: () => void; - onConfirm: () => void; -} - export interface InsertMenuProps { isUploading?: boolean; location?: Location; diff --git a/web/src/components/MemoEditor/types/index.ts b/web/src/components/MemoEditor/types/index.ts index 758a21d9..6b81f232 100644 --- a/web/src/components/MemoEditor/types/index.ts +++ b/web/src/components/MemoEditor/types/index.ts @@ -8,8 +8,6 @@ export type { FocusModeExitButtonProps, FocusModeOverlayProps, InsertMenuProps, - LinkMemoDialogProps, - LocationDialogProps, MemoEditorProps, SlashCommandsProps, TagSuggestionsProps, diff --git a/web/src/components/MemoView/components/metadata/AttachmentCard.tsx b/web/src/components/MemoMetadata/Attachment/AttachmentCard.tsx similarity index 100% rename from web/src/components/MemoView/components/metadata/AttachmentCard.tsx rename to web/src/components/MemoMetadata/Attachment/AttachmentCard.tsx diff --git a/web/src/components/MemoEditor/components/AttachmentList.tsx b/web/src/components/MemoMetadata/Attachment/AttachmentListEditor.tsx similarity index 90% rename from web/src/components/MemoEditor/components/AttachmentList.tsx rename to web/src/components/MemoMetadata/Attachment/AttachmentListEditor.tsx index e8cf2c39..ea2a416b 100644 --- a/web/src/components/MemoEditor/components/AttachmentList.tsx +++ b/web/src/components/MemoMetadata/Attachment/AttachmentListEditor.tsx @@ -1,12 +1,13 @@ import { ChevronDownIcon, ChevronUpIcon, FileIcon, PaperclipIcon, XIcon } from "lucide-react"; import type { FC } from "react"; +import type { LocalFile } from "@/components/MemoEditor/types/attachment"; +import { toAttachmentItems } from "@/components/MemoEditor/types/attachment"; import { cn } from "@/lib/utils"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { formatFileSize, getFileTypeLabel } from "@/utils/format"; -import type { LocalFile } from "../types/attachment"; -import { toAttachmentItems } from "../types/attachment"; +import SectionHeader from "../SectionHeader"; -interface AttachmentListProps { +interface AttachmentListEditorProps { attachments: Attachment[]; localFiles?: LocalFile[]; onAttachmentsChange?: (attachments: Attachment[]) => void; @@ -100,7 +101,7 @@ const AttachmentItemCard: FC<{ ); }; -const AttachmentList: FC = ({ attachments, localFiles = [], onAttachmentsChange, onRemoveLocalFile }) => { +const AttachmentListEditor: FC = ({ attachments, localFiles = [], onAttachmentsChange, onRemoveLocalFile }) => { if (attachments.length === 0 && localFiles.length === 0) { return null; } @@ -139,10 +140,7 @@ const AttachmentList: FC = ({ attachments, localFiles = [], return (
-
- - Attachments ({items.length}) -
+
{items.map((item) => { @@ -166,4 +164,4 @@ const AttachmentList: FC = ({ attachments, localFiles = [], ); }; -export default AttachmentList; +export default AttachmentListEditor; diff --git a/web/src/components/MemoView/components/metadata/AttachmentList.tsx b/web/src/components/MemoMetadata/Attachment/AttachmentListView.tsx similarity index 86% rename from web/src/components/MemoView/components/metadata/AttachmentList.tsx rename to web/src/components/MemoMetadata/Attachment/AttachmentListView.tsx index c3305e56..1e4b3dd9 100644 --- a/web/src/components/MemoView/components/metadata/AttachmentList.tsx +++ b/web/src/components/MemoMetadata/Attachment/AttachmentListView.tsx @@ -1,14 +1,15 @@ import { FileAudioIcon, FileIcon, PaperclipIcon } from "lucide-react"; import { useMemo } from "react"; +import { cn } from "@/lib/utils"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; import { formatFileSize, getFileTypeLabel } from "@/utils/format"; -import { useMemoViewContext } from "../../MemoViewContext"; +import SectionHeader from "../SectionHeader"; import AttachmentCard from "./AttachmentCard"; -import SectionHeader from "./SectionHeader"; -interface AttachmentListProps { +interface AttachmentListViewProps { attachments: Attachment[]; + onImagePreview?: (urls: string[], index: number) => void; } const isImageAttachment = (attachment: Attachment): boolean => getAttachmentType(attachment) === "image/*"; @@ -79,19 +80,24 @@ const AudioItem = ({ attachment }: { attachment: Attachment }) => { interface VisualItemProps { attachment: Attachment; - onImageClick: (url: string) => void; + onImageClick?: (url: string) => void; } const VisualItem = ({ attachment, onImageClick }: VisualItemProps) => { + const isInteractive = isImageAttachment(attachment) && Boolean(onImageClick); + const handleClick = () => { - if (isImageAttachment(attachment)) { - onImageClick(getAttachmentUrl(attachment)); + if (isInteractive) { + onImageClick?.(getAttachmentUrl(attachment)); } }; return (
@@ -99,7 +105,7 @@ const VisualItem = ({ attachment, onImageClick }: VisualItemProps) => { ); }; -const VisualGrid = ({ attachments, onImageClick }: { attachments: Attachment[]; onImageClick: (url: string) => void }) => ( +const VisualGrid = ({ attachments, onImageClick }: { attachments: Attachment[]; onImageClick?: (url: string) => void }) => (
{attachments.map((attachment) => ( @@ -127,9 +133,7 @@ const DocsList = ({ attachments }: { attachments: Attachment[] }) => ( const Divider = () =>
; -const AttachmentList = ({ attachments }: AttachmentListProps) => { - const { openPreview } = useMemoViewContext(); - +const AttachmentListView = ({ attachments, onImagePreview }: AttachmentListViewProps) => { const { visual, audio, docs } = useMemo(() => separateAttachments(attachments), [attachments]); const imageAttachments = useMemo(() => visual.filter(isImageAttachment), [visual]); @@ -141,7 +145,7 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => { const handleImageClick = (imgUrl: string) => { const index = imageUrls.findIndex((url) => url === imgUrl); - openPreview(imageUrls, index >= 0 ? index : 0); + onImagePreview?.(imageUrls, index >= 0 ? index : 0); }; const sections = [visual.length > 0, audio.length > 0, docs.length > 0]; @@ -166,4 +170,4 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => { ); }; -export default AttachmentList; +export default AttachmentListView; diff --git a/web/src/components/MemoMetadata/Attachment/index.ts b/web/src/components/MemoMetadata/Attachment/index.ts new file mode 100644 index 00000000..c9895f1e --- /dev/null +++ b/web/src/components/MemoMetadata/Attachment/index.ts @@ -0,0 +1,3 @@ +export { default as AttachmentCard } from "./AttachmentCard"; +export { default as AttachmentListEditor } from "./AttachmentListEditor"; +export { default as AttachmentListView } from "./AttachmentListView"; diff --git a/web/src/components/MemoEditor/components/LocationDialog.tsx b/web/src/components/MemoMetadata/Location/LocationDialog.tsx similarity index 89% rename from web/src/components/MemoEditor/components/LocationDialog.tsx rename to web/src/components/MemoMetadata/Location/LocationDialog.tsx index 8b670a64..bc8822e3 100644 --- a/web/src/components/MemoEditor/components/LocationDialog.tsx +++ b/web/src/components/MemoMetadata/Location/LocationDialog.tsx @@ -1,3 +1,5 @@ +import type { LatLng } from "leaflet"; +import type { LocationState } from "@/components/MemoEditor/types/insert-menu"; import { LocationPicker } from "@/components/map"; import { Button } from "@/components/ui/button"; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog"; @@ -6,7 +8,17 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { VisuallyHidden } from "@/components/ui/visually-hidden"; import { useTranslate } from "@/utils/i18n"; -import type { LocationDialogProps } from "../types"; + +interface LocationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + state: LocationState; + onPositionChange: (position: LatLng) => void; + onUpdateCoordinate: (type: "lat" | "lng", value: string) => void; + onPlaceholderChange: (placeholder: string) => void; + onCancel: () => void; + onConfirm: () => void; +} export const LocationDialog = ({ open, diff --git a/web/src/components/MemoEditor/components/LocationDisplay.tsx b/web/src/components/MemoMetadata/Location/LocationDisplayEditor.tsx similarity index 89% rename from web/src/components/MemoEditor/components/LocationDisplay.tsx rename to web/src/components/MemoMetadata/Location/LocationDisplayEditor.tsx index 0daf6079..bb8c956d 100644 --- a/web/src/components/MemoEditor/components/LocationDisplay.tsx +++ b/web/src/components/MemoMetadata/Location/LocationDisplayEditor.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 LocationDisplayProps { +interface LocationDisplayEditorProps { location: Location; onRemove?: () => void; className?: string; } -const LocationDisplay: FC = ({ location, onRemove, className }) => { +const LocationDisplayEditor: FC = ({ location, onRemove, className }) => { const displayText = location.placeholder || `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`; return ( @@ -45,4 +45,4 @@ const LocationDisplay: FC = ({ location, onRemove, classNa ); }; -export default LocationDisplay; +export default LocationDisplayEditor; diff --git a/web/src/components/MemoView/components/metadata/LocationDisplay.tsx b/web/src/components/MemoMetadata/Location/LocationDisplayView.tsx similarity index 87% rename from web/src/components/MemoView/components/metadata/LocationDisplay.tsx rename to web/src/components/MemoMetadata/Location/LocationDisplayView.tsx index 7591a8ae..378bee9d 100644 --- a/web/src/components/MemoView/components/metadata/LocationDisplay.tsx +++ b/web/src/components/MemoMetadata/Location/LocationDisplayView.tsx @@ -2,16 +2,16 @@ import { LatLng } from "leaflet"; import { MapPinIcon } from "lucide-react"; import { useState } from "react"; import { LocationPicker } from "@/components/map"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; import type { Location } from "@/types/proto/api/v1/memo_service_pb"; -import { Popover, PopoverContent, PopoverTrigger } from "../../../ui/popover"; -interface LocationDisplayProps { +interface LocationDisplayViewProps { location?: Location; className?: string; } -const LocationDisplay = ({ location, className }: LocationDisplayProps) => { +const LocationDisplayView = ({ location, className }: LocationDisplayViewProps) => { const [popoverOpen, setPopoverOpen] = useState(false); if (!location) { @@ -49,4 +49,4 @@ const LocationDisplay = ({ location, className }: LocationDisplayProps) => { ); }; -export default LocationDisplay; +export default LocationDisplayView; diff --git a/web/src/components/MemoMetadata/Location/index.ts b/web/src/components/MemoMetadata/Location/index.ts new file mode 100644 index 00000000..ad57e263 --- /dev/null +++ b/web/src/components/MemoMetadata/Location/index.ts @@ -0,0 +1,3 @@ +export { LocationDialog } from "./LocationDialog"; +export { default as LocationDisplayEditor } from "./LocationDisplayEditor"; +export { default as LocationDisplayView } from "./LocationDisplayView"; diff --git a/web/src/components/MemoEditor/components/LinkMemoDialog.tsx b/web/src/components/MemoMetadata/Relation/LinkMemoDialog.tsx similarity index 90% rename from web/src/components/MemoEditor/components/LinkMemoDialog.tsx rename to web/src/components/MemoMetadata/Relation/LinkMemoDialog.tsx index 029e1ccb..cbef9c9b 100644 --- a/web/src/components/MemoEditor/components/LinkMemoDialog.tsx +++ b/web/src/components/MemoMetadata/Relation/LinkMemoDialog.tsx @@ -6,8 +6,19 @@ 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"; -import type { LinkMemoDialogProps } from "../types"; + +interface LinkMemoDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + searchText: string; + onSearchChange: (text: string) => void; + filteredMemos: Memo[]; + isFetching: boolean; + onSelectMemo: (memo: Memo) => void; + isAlreadyLinked: (memoName: string) => boolean; +} export const LinkMemoDialog = ({ open, diff --git a/web/src/components/MemoView/components/metadata/RelationCard.tsx b/web/src/components/MemoMetadata/Relation/RelationCard.tsx similarity index 84% rename from web/src/components/MemoView/components/metadata/RelationCard.tsx rename to web/src/components/MemoMetadata/Relation/RelationCard.tsx index 72971916..24069678 100644 --- a/web/src/components/MemoView/components/metadata/RelationCard.tsx +++ b/web/src/components/MemoMetadata/Relation/RelationCard.tsx @@ -1,5 +1,5 @@ +import MemoSnippetLink from "@/components/MemoView/components/MemoSnippetLink"; import type { MemoRelation_Memo } from "@/types/proto/api/v1/memo_service_pb"; -import MemoSnippetLink from "../MemoSnippetLink"; interface RelationCardProps { memo: MemoRelation_Memo; diff --git a/web/src/components/MemoEditor/components/RelationList.tsx b/web/src/components/MemoMetadata/Relation/RelationListEditor.tsx similarity index 80% rename from web/src/components/MemoEditor/components/RelationList.tsx rename to web/src/components/MemoMetadata/Relation/RelationListEditor.tsx index eef35627..aca304c3 100644 --- a/web/src/components/MemoEditor/components/RelationList.tsx +++ b/web/src/components/MemoMetadata/Relation/RelationListEditor.tsx @@ -1,13 +1,14 @@ import { create } from "@bufbuild/protobuf"; import { LinkIcon, XIcon } from "lucide-react"; import type { FC } from "react"; -import { useEffect, useState } from "react"; -import RelationCard from "@/components/MemoView/components/metadata/RelationCard"; +import { useEffect, useMemo, useState } from "react"; import { memoServiceClient } from "@/connect"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { MemoRelation_Memo, MemoRelation_MemoSchema, MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; +import SectionHeader from "../SectionHeader"; +import RelationCard from "./RelationCard"; -interface RelationListProps { +interface RelationListEditorProps { relations: MemoRelation[]; onRelationsChange?: (relations: MemoRelation[]) => void; parentPage?: string; @@ -38,9 +39,10 @@ const RelationItemCard: FC<{ ); }; -const RelationList: FC = ({ relations, onRelationsChange, parentPage, memoName }) => { - const referenceRelations = relations.filter( - (r) => r.type === MemoRelation_Type.REFERENCE && (!memoName || !r.memo?.name || r.memo.name === memoName), +const RelationListEditor: FC = ({ relations, onRelationsChange, parentPage, memoName }) => { + const referenceRelations = useMemo( + () => relations.filter((r) => r.type === MemoRelation_Type.REFERENCE && (!memoName || !r.memo?.name || r.memo.name === memoName)), + [relations, memoName], ); const [fetchedMemos, setFetchedMemos] = useState>({}); @@ -76,10 +78,7 @@ const RelationList: FC = ({ relations, onRelationsChange, par return (
-
- - Relations ({referenceRelations.length}) -
+
{referenceRelations.map((relation) => { @@ -92,4 +91,4 @@ const RelationList: FC = ({ relations, onRelationsChange, par ); }; -export default RelationList; +export default RelationListEditor; diff --git a/web/src/components/MemoView/components/metadata/RelationList.tsx b/web/src/components/MemoMetadata/Relation/RelationListView.tsx similarity index 93% rename from web/src/components/MemoView/components/metadata/RelationList.tsx rename to web/src/components/MemoMetadata/Relation/RelationListView.tsx index 4ce02cbd..8ac60ed6 100644 --- a/web/src/components/MemoView/components/metadata/RelationList.tsx +++ b/web/src/components/MemoMetadata/Relation/RelationListView.tsx @@ -4,17 +4,17 @@ import { cn } from "@/lib/utils"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; +import SectionHeader from "../SectionHeader"; import RelationCard from "./RelationCard"; -import SectionHeader from "./SectionHeader"; -interface RelationListProps { +interface RelationListViewProps { relations: MemoRelation[]; currentMemoName?: string; parentPage?: string; className?: string; } -function RelationList({ relations, currentMemoName, parentPage, className }: RelationListProps) { +function RelationListView({ relations, currentMemoName, parentPage, className }: RelationListViewProps) { const t = useTranslate(); const [activeTab, setActiveTab] = useState<"referencing" | "referenced">("referencing"); @@ -81,4 +81,4 @@ function RelationList({ relations, currentMemoName, parentPage, className }: Rel ); } -export default RelationList; +export default RelationListView; diff --git a/web/src/components/MemoMetadata/Relation/index.ts b/web/src/components/MemoMetadata/Relation/index.ts new file mode 100644 index 00000000..b46fac0c --- /dev/null +++ b/web/src/components/MemoMetadata/Relation/index.ts @@ -0,0 +1,4 @@ +export { LinkMemoDialog } from "./LinkMemoDialog"; +export { default as RelationCard } from "./RelationCard"; +export { default as RelationListEditor } from "./RelationListEditor"; +export { default as RelationListView } from "./RelationListView"; diff --git a/web/src/components/MemoView/components/metadata/SectionHeader.tsx b/web/src/components/MemoMetadata/SectionHeader.tsx similarity index 100% rename from web/src/components/MemoView/components/metadata/SectionHeader.tsx rename to web/src/components/MemoMetadata/SectionHeader.tsx diff --git a/web/src/components/MemoMetadata/index.ts b/web/src/components/MemoMetadata/index.ts new file mode 100644 index 00000000..5db1c08f --- /dev/null +++ b/web/src/components/MemoMetadata/index.ts @@ -0,0 +1,6 @@ +// Consolidated memo metadata components for attachments, locations, and relations. + +export { AttachmentCard, AttachmentListEditor, AttachmentListView } from "./Attachment"; +export { LocationDialog, LocationDisplayEditor, LocationDisplayView } from "./Location"; +export { LinkMemoDialog, RelationCard, RelationListEditor, RelationListView } from "./Relation"; +export { default as SectionHeader } from "./SectionHeader"; diff --git a/web/src/components/MemoResource.tsx b/web/src/components/MemoResource.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx index 8d450643..ac3fa109 100644 --- a/web/src/components/MemoView/components/MemoBody.tsx +++ b/web/src/components/MemoView/components/MemoBody.tsx @@ -1,3 +1,4 @@ +import { AttachmentListView, LocationDisplayView, RelationListView } from "@/components/MemoMetadata"; import { cn } from "@/lib/utils"; import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; @@ -6,7 +7,6 @@ import { MemoReactionListView } from "../../MemoReactionListView"; import { useMemoHandlers } from "../hooks"; import { useMemoViewContext } from "../MemoViewContext"; import type { MemoBodyProps } from "../types"; -import { AttachmentList, LocationDisplay, RelationList } from "./metadata"; const NsfwOverlay: React.FC<{ onClick?: () => void }> = ({ onClick }) => { const t = useTranslate(); @@ -44,9 +44,9 @@ const MemoBody: React.FC = ({ compact }) => { onDoubleClick={handleMemoContentDoubleClick} compact={memo.pinned ? false : compact} // Always show full content when pinned /> - - - {memo.location && } + + + {memo.location && }
diff --git a/web/src/components/MemoView/components/metadata/index.ts b/web/src/components/MemoView/components/metadata/index.ts deleted file mode 100644 index 634e4563..00000000 --- a/web/src/components/MemoView/components/metadata/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as AttachmentCard } from "./AttachmentCard"; -export { default as AttachmentList } from "./AttachmentList"; -export { default as LocationDisplay } from "./LocationDisplay"; - -export { default as RelationCard } from "./RelationCard"; -export { default as RelationList } from "./RelationList"; diff --git a/web/src/pages/SharedMemo.tsx b/web/src/pages/SharedMemo.tsx index 493490a6..1cdb92ea 100644 --- a/web/src/pages/SharedMemo.tsx +++ b/web/src/pages/SharedMemo.tsx @@ -4,7 +4,9 @@ import { Code, ConnectError } from "@connectrpc/connect"; import { AlertCircleIcon } from "lucide-react"; import { useParams } from "react-router-dom"; import MemoContent from "@/components/MemoContent"; -import AttachmentList from "@/components/MemoView/components/metadata/AttachmentList"; +import { AttachmentListView } from "@/components/MemoMetadata"; +import { useImagePreview } from "@/components/MemoView/hooks"; +import PreviewImageDialog from "@/components/PreviewImageDialog"; import UserAvatar from "@/components/UserAvatar"; import { useSharedMemo } from "@/hooks/useMemoShareQueries"; import { useUser } from "@/hooks/useUserQueries"; @@ -22,6 +24,7 @@ function withShareAttachmentLinks(attachments: Attachment[], token: string): Att const SharedMemo = () => { const t = useTranslate(); const { token = "" } = useParams<{ token: string }>(); + const { previewState, openPreview, setPreviewOpen } = useImagePreview(); const { data: memo, error, isLoading } = useSharedMemo(token, { enabled: !!token }); const { data: creator } = useUser(memo?.creator ?? "", { enabled: !!memo?.creator }); @@ -64,8 +67,17 @@ const SharedMemo = () => {
- {memo.attachments.length > 0 && } + {memo.attachments.length > 0 && ( + + )}
+ +
); };