diff --git a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx index f36aa5640..02d3ed893 100644 --- a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx +++ b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx @@ -4,7 +4,6 @@ import { FileIcon, LinkIcon, LoaderIcon, MapPinIcon, Maximize2Icon, MoreHorizont import { useEffect, useState } from "react"; import { useDebounce } from "react-use"; import { useReverseGeocoding } from "@/components/map"; -import type { LocalFile } from "@/components/memo-metadata"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -22,6 +21,7 @@ import { LinkMemoDialog, LocationDialog } from "../components"; import { useFileUpload, useLinkMemo, useLocation } from "../hooks"; import { useEditorContext } from "../state"; import type { InsertMenuProps } from "../types"; +import type { LocalFile } from "../types/attachment"; const InsertMenu = (props: InsertMenuProps) => { const t = useTranslate(); diff --git a/web/src/components/MemoEditor/components/AttachmentList.tsx b/web/src/components/MemoEditor/components/AttachmentList.tsx index 55f7a4666..240bba8eb 100644 --- a/web/src/components/MemoEditor/components/AttachmentList.tsx +++ b/web/src/components/MemoEditor/components/AttachmentList.tsx @@ -1,10 +1,10 @@ import { ChevronDownIcon, ChevronUpIcon, FileIcon, Loader2Icon, PaperclipIcon, XIcon } from "lucide-react"; import type { FC } from "react"; -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"; +import type { LocalFile } from "../types/attachment"; +import { toAttachmentItems } from "../types/attachment"; interface AttachmentListProps { attachments: Attachment[]; diff --git a/web/src/components/MemoEditor/components/EditorContent.tsx b/web/src/components/MemoEditor/components/EditorContent.tsx index e371dd600..6abd22ab8 100644 --- a/web/src/components/MemoEditor/components/EditorContent.tsx +++ b/web/src/components/MemoEditor/components/EditorContent.tsx @@ -1,9 +1,9 @@ import { forwardRef } from "react"; -import type { LocalFile } from "@/components/memo-metadata"; import Editor, { type EditorRefActions } from "../Editor"; import { useBlobUrls, useDragAndDrop } from "../hooks"; import { useEditorContext } from "../state"; import type { EditorContentProps } from "../types"; +import type { LocalFile } from "../types/attachment"; export const EditorContent = forwardRef(({ placeholder }, ref) => { const { state, actions, dispatch } = useEditorContext(); diff --git a/web/src/components/MemoEditor/hooks/useFileUpload.ts b/web/src/components/MemoEditor/hooks/useFileUpload.ts index 2778da0b7..66bda31c9 100644 --- a/web/src/components/MemoEditor/hooks/useFileUpload.ts +++ b/web/src/components/MemoEditor/hooks/useFileUpload.ts @@ -1,5 +1,5 @@ import { useRef } from "react"; -import type { LocalFile } from "@/components/memo-metadata"; +import type { LocalFile } from "../types/attachment"; export const useFileUpload = (onFilesSelected: (localFiles: LocalFile[]) => void) => { const fileInputRef = useRef(null); diff --git a/web/src/components/MemoEditor/services/uploadService.ts b/web/src/components/MemoEditor/services/uploadService.ts index d579c92db..6c466f66d 100644 --- a/web/src/components/MemoEditor/services/uploadService.ts +++ b/web/src/components/MemoEditor/services/uploadService.ts @@ -1,8 +1,8 @@ import { create } from "@bufbuild/protobuf"; -import type { LocalFile } from "@/components/memo-metadata"; import { attachmentServiceClient } from "@/connect"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { AttachmentSchema } from "@/types/proto/api/v1/attachment_service_pb"; +import type { LocalFile } from "../types/attachment"; export const uploadService = { async uploadFiles(localFiles: LocalFile[]): Promise { diff --git a/web/src/components/MemoEditor/state/actions.ts b/web/src/components/MemoEditor/state/actions.ts index 89296f7da..ec46bd05a 100644 --- a/web/src/components/MemoEditor/state/actions.ts +++ b/web/src/components/MemoEditor/state/actions.ts @@ -1,6 +1,6 @@ -import type { LocalFile } from "@/components/memo-metadata"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; +import type { LocalFile } from "../types/attachment"; import type { EditorAction, EditorState, LoadingKey } from "./types"; export const editorActions = { diff --git a/web/src/components/MemoEditor/state/types.ts b/web/src/components/MemoEditor/state/types.ts index a0bd33380..48289b210 100644 --- a/web/src/components/MemoEditor/state/types.ts +++ b/web/src/components/MemoEditor/state/types.ts @@ -1,7 +1,7 @@ -import type { LocalFile } from "@/components/memo-metadata"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import type { Location, MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; import { Visibility } from "@/types/proto/api/v1/memo_service_pb"; +import type { LocalFile } from "../types/attachment"; export type LoadingKey = "saving" | "uploading" | "loading"; diff --git a/web/src/components/memo-metadata/types.ts b/web/src/components/MemoEditor/types/attachment.ts similarity index 89% rename from web/src/components/memo-metadata/types.ts rename to web/src/components/MemoEditor/types/attachment.ts index 1e7a6bf90..78dd984ce 100644 --- a/web/src/components/memo-metadata/types.ts +++ b/web/src/components/MemoEditor/types/attachment.ts @@ -1,16 +1,9 @@ import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import { getAttachmentThumbnailUrl, getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; -export type DisplayMode = "edit" | "view"; - -export interface BaseMetadataProps { - mode: DisplayMode; - className?: string; -} - export type FileCategory = "image" | "video" | "document"; -// Pure view model for rendering attachments and local files +// Unified view model for rendering attachments and local files export interface AttachmentItem { readonly id: string; readonly filename: string; @@ -22,6 +15,12 @@ export interface AttachmentItem { readonly isLocal: boolean; } +// For MemoEditor: local files being uploaded +export interface LocalFile { + readonly file: File; + readonly previewUrl: string; +} + function categorizeFile(mimeType: string): FileCategory { if (mimeType.startsWith("image/")) return "image"; if (mimeType.startsWith("video/")) return "video"; @@ -46,7 +45,7 @@ export function attachmentToItem(attachment: Attachment): AttachmentItem { export function fileToItem(file: File, blobUrl: string): AttachmentItem { return { - id: blobUrl, // Use blob URL as unique ID since we don't have a server ID yet + id: blobUrl, filename: file.name, category: categorizeFile(file.type), mimeType: file.type, @@ -57,11 +56,6 @@ export function fileToItem(file: File, blobUrl: string): AttachmentItem { }; } -export interface LocalFile { - readonly file: File; - readonly previewUrl: string; -} - export function toAttachmentItems(attachments: Attachment[], localFiles: LocalFile[] = []): AttachmentItem[] { return [...attachments.map(attachmentToItem), ...localFiles.map(({ file, previewUrl }) => fileToItem(file, previewUrl))]; } diff --git a/web/src/components/MemoEditor/types/context.ts b/web/src/components/MemoEditor/types/context.ts index 582d78545..456a9fdee 100644 --- a/web/src/components/MemoEditor/types/context.ts +++ b/web/src/components/MemoEditor/types/context.ts @@ -1,7 +1,7 @@ import { createContext } from "react"; import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb"; -import type { LocalFile } from "../../memo-metadata"; +import type { LocalFile } from "./attachment"; export interface MemoEditorContextValue { attachmentList: Attachment[]; diff --git a/web/src/components/MemoView/components/MemoBody.tsx b/web/src/components/MemoView/components/MemoBody.tsx index 7e7eb4207..66c92fc4f 100644 --- a/web/src/components/MemoView/components/MemoBody.tsx +++ b/web/src/components/MemoView/components/MemoBody.tsx @@ -3,9 +3,9 @@ import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; import MemoContent from "../../MemoContent"; import { MemoReactionListView } from "../../MemoReactionListView"; -import { AttachmentList, LocationDisplay, RelationList } from "../../memo-metadata"; import { useMemoViewContext } from "../MemoViewContext"; import type { MemoBodyProps } from "../types"; +import { AttachmentList, LocationDisplay, RelationList } from "./metadata"; const MemoBody: React.FC = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => { const t = useTranslate(); diff --git a/web/src/components/memo-metadata/AttachmentCard.tsx b/web/src/components/MemoView/components/metadata/AttachmentCard.tsx similarity index 51% rename from web/src/components/memo-metadata/AttachmentCard.tsx rename to web/src/components/MemoView/components/metadata/AttachmentCard.tsx index baff7cb66..2aadfb76c 100644 --- a/web/src/components/memo-metadata/AttachmentCard.tsx +++ b/web/src/components/MemoView/components/metadata/AttachmentCard.tsx @@ -1,21 +1,22 @@ import { cn } from "@/lib/utils"; -import type { AttachmentItem } from "./types"; +import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb"; +import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; interface AttachmentCardProps { - item: AttachmentItem; - mode: "view"; + attachment: Attachment; onClick?: () => void; className?: string; } -const AttachmentCard = ({ item, onClick, className }: AttachmentCardProps) => { - const { category, filename, sourceUrl } = item; +const AttachmentCard = ({ attachment, onClick, className }: AttachmentCardProps) => { + const attachmentType = getAttachmentType(attachment); + const sourceUrl = getAttachmentUrl(attachment); - if (category === "image") { + if (attachmentType === "image/*") { return ( {filename} { ); } - if (category === "video") { + if (attachmentType === "video/*") { return