diff --git a/web/src/components/MemoEditor/Editor/CommandSuggestions.tsx b/web/src/components/MemoEditor/Editor/CommandSuggestions.tsx index 59a750ad1..731426956 100644 --- a/web/src/components/MemoEditor/Editor/CommandSuggestions.tsx +++ b/web/src/components/MemoEditor/Editor/CommandSuggestions.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react-lite"; import OverflowTip from "@/components/kit/OverflowTip"; -import { Command } from "../types/command"; -import { EditorRefActions } from "."; +import type { EditorRefActions } from "."; +import type { Command } from "./commands"; import { SuggestionsPopup } from "./SuggestionsPopup"; import { useSuggestions } from "./useSuggestions"; diff --git a/web/src/components/MemoEditor/Editor/commands.ts b/web/src/components/MemoEditor/Editor/commands.ts index 4cf7e225b..6c863fa5c 100644 --- a/web/src/components/MemoEditor/Editor/commands.ts +++ b/web/src/components/MemoEditor/Editor/commands.ts @@ -1,4 +1,11 @@ -import { Command } from "@/components/MemoEditor/types/command"; +/** + * Command type for slash commands in the editor + */ +export interface Command { + name: string; + run: () => string; + cursorOffset?: number; +} export const editorCommands: Command[] = [ { diff --git a/web/src/components/MemoEditor/Editor/index.tsx b/web/src/components/MemoEditor/Editor/index.tsx index c9546617e..290e9eabe 100644 --- a/web/src/components/MemoEditor/Editor/index.tsx +++ b/web/src/components/MemoEditor/Editor/index.tsx @@ -1,7 +1,6 @@ -import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from "react"; import { cn } from "@/lib/utils"; import { EDITOR_HEIGHT } from "../constants"; -import { Command } from "../types/command"; import CommandSuggestions from "./CommandSuggestions"; import { editorCommands } from "./commands"; import TagSuggestions from "./TagSuggestions"; @@ -27,8 +26,6 @@ interface Props { className: string; initialContent: string; placeholder: string; - tools?: ReactNode; - commands?: Command[]; onContentChange: (content: string) => void; onPaste: (event: React.ClipboardEvent) => void; /** Whether Focus Mode is active - adjusts height constraints for immersive writing */ diff --git a/web/src/components/MemoEditor/Editor/markdownShortcuts.ts b/web/src/components/MemoEditor/Editor/markdownShortcuts.ts index eb0c04e0e..4192042cb 100644 --- a/web/src/components/MemoEditor/Editor/markdownShortcuts.ts +++ b/web/src/components/MemoEditor/Editor/markdownShortcuts.ts @@ -3,8 +3,6 @@ import type { EditorRefActions } from "./index"; /** * Handles keyboard shortcuts for markdown formatting * Requires Cmd/Ctrl key to be pressed - * - * @alias handleEditorKeydownWithMarkdownShortcuts - for backward compatibility */ export function handleMarkdownShortcuts(event: React.KeyboardEvent, editor: EditorRefActions): void { switch (event.key.toLowerCase()) { @@ -23,9 +21,6 @@ export function handleMarkdownShortcuts(event: React.KeyboardEvent, editor: Edit } } -// Backward compatibility alias -export const handleEditorKeydownWithMarkdownShortcuts = handleMarkdownShortcuts; - /** * Inserts a hyperlink for the selected text * If selected text is a URL, creates a link with empty text diff --git a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx index a8f43e464..ae138c179 100644 --- a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx +++ b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx @@ -197,7 +197,6 @@ const InsertMenu = observer((props: Props) => { filteredMemos={linkMemo.filteredMemos} isFetching={linkMemo.isFetching} onSelectMemo={linkMemo.addMemoRelation} - getHighlightedContent={linkMemo.getHighlightedContent} /> 20) { + before = "..." + before.slice(before.length - 20); + } + const highlighted = content.slice(index, index + searchText.length); + let after = content.slice(index + searchText.length); + if (after.length > 20) { + after = after.slice(0, 20) + "..."; + } + + return ( + <> + {before} + {highlighted} + {after} + + ); +} + interface LinkMemoDialogProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -11,7 +39,6 @@ interface LinkMemoDialogProps { filteredMemos: Memo[]; isFetching: boolean; onSelectMemo: (memo: Memo) => void; - getHighlightedContent: (content: string) => React.ReactNode; } export const LinkMemoDialog = ({ @@ -22,7 +49,6 @@ export const LinkMemoDialog = ({ filteredMemos, isFetching, onSelectMemo, - getHighlightedContent, }: LinkMemoDialogProps) => { const t = useTranslate(); @@ -54,7 +80,7 @@ export const LinkMemoDialog = ({

{memo.displayTime?.toLocaleString()}

- {searchText ? getHighlightedContent(memo.content) : memo.snippet} + {searchText ? highlightSearchText(memo.content, searchText) : memo.snippet}

diff --git a/web/src/components/MemoEditor/hooks/index.ts b/web/src/components/MemoEditor/hooks/index.ts index 38da0cd6c..82ad7c42f 100644 --- a/web/src/components/MemoEditor/hooks/index.ts +++ b/web/src/components/MemoEditor/hooks/index.ts @@ -1,19 +1,14 @@ -// Custom hooks for MemoEditor +// Custom hooks for MemoEditor (internal use only) export { useAbortController } from "./useAbortController"; export { useBlobUrls } from "./useBlobUrls"; -export { useDebounce } from "./useDebounce"; export { useDragAndDrop } from "./useDragAndDrop"; export { useFileUpload } from "./useFileUpload"; export { useFocusMode } from "./useFocusMode"; export { useLinkMemo } from "./useLinkMemo"; export { useLocalFileManager } from "./useLocalFileManager"; export { useLocation } from "./useLocation"; -export type { UseMemoEditorHandlersOptions, UseMemoEditorHandlersReturn } from "./useMemoEditorHandlers"; export { useMemoEditorHandlers } from "./useMemoEditorHandlers"; -export type { UseMemoEditorInitOptions, UseMemoEditorInitReturn } from "./useMemoEditorInit"; export { useMemoEditorInit } from "./useMemoEditorInit"; -export type { UseMemoEditorKeyboardOptions } from "./useMemoEditorKeyboard"; export { useMemoEditorKeyboard } from "./useMemoEditorKeyboard"; -export type { UseMemoEditorStateReturn } from "./useMemoEditorState"; export { useMemoEditorState } from "./useMemoEditorState"; export { useMemoSave } from "./useMemoSave"; diff --git a/web/src/components/MemoEditor/hooks/useAbortController.ts b/web/src/components/MemoEditor/hooks/useAbortController.ts index ec8843afa..d65b6c84f 100644 --- a/web/src/components/MemoEditor/hooks/useAbortController.ts +++ b/web/src/components/MemoEditor/hooks/useAbortController.ts @@ -1,78 +1,23 @@ import { useEffect, useRef } from "react"; /** - * Custom hook for managing AbortController lifecycle - * Useful for canceling async operations like fetch requests - * - * @returns Object with methods to create and abort requests - * - * @example - * ```tsx - * const { getSignal, abort, abortAndCreate } = useAbortController(); - * - * // Create signal for fetch - * const signal = getSignal(); - * fetch(url, { signal }); - * - * // Cancel on user action - * abort(); - * - * // Or cancel previous and create new - * const newSignal = abortAndCreate(); - * fetch(newUrl, { signal: newSignal }); - * ``` + * Hook for managing AbortController lifecycle */ export function useAbortController() { const controllerRef = useRef(null); - // Clean up on unmount - useEffect(() => { - return () => { - controllerRef.current?.abort(); - }; - }, []); + useEffect(() => () => controllerRef.current?.abort(), []); - /** - * Aborts the current request if one exists - */ - const abort = (): void => { + const abort = () => { controllerRef.current?.abort(); controllerRef.current = null; }; - /** - * Creates a new AbortController and returns its signal - * Does not abort previous controller - */ - const create = (): AbortSignal => { - const controller = new AbortController(); - controllerRef.current = controller; - return controller.signal; - }; - - /** - * Aborts current request and creates a new AbortController - * Useful for debounced requests - */ const abortAndCreate = (): AbortSignal => { abort(); - return create(); - }; - - /** - * Gets the signal from the current controller, or creates new one - */ - const getSignal = (): AbortSignal => { - if (!controllerRef.current) { - return create(); - } + controllerRef.current = new AbortController(); return controllerRef.current.signal; }; - return { - abort, - create, - abortAndCreate, - getSignal, - }; + return { abort, abortAndCreate }; } diff --git a/web/src/components/MemoEditor/hooks/useBlobUrls.ts b/web/src/components/MemoEditor/hooks/useBlobUrls.ts index b2cf6fbf9..4e0db7fff 100644 --- a/web/src/components/MemoEditor/hooks/useBlobUrls.ts +++ b/web/src/components/MemoEditor/hooks/useBlobUrls.ts @@ -1,65 +1,31 @@ import { useEffect, useRef } from "react"; /** - * Custom hook for managing blob URLs lifecycle - * Automatically tracks and cleans up all blob URLs on unmount to prevent memory leaks - * - * @returns Object with methods to create, revoke, and manage blob URLs - * - * @example - * ```tsx - * const { createBlobUrl, revokeBlobUrl, revokeAll } = useBlobUrls(); - * - * // Create blob URL (automatically tracked) - * const url = createBlobUrl(file); - * - * // Manually revoke when needed - * revokeBlobUrl(url); - * - * // All URLs are automatically revoked on unmount - * ``` + * Hook for managing blob URLs lifecycle with automatic cleanup */ export function useBlobUrls() { - const blobUrlsRef = useRef>(new Set()); + const urlsRef = useRef>(new Set()); - // Clean up all blob URLs on unmount - useEffect(() => { - return () => { - blobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)); - blobUrlsRef.current.clear(); - }; - }, []); - - /** - * Creates a blob URL from a file or blob and tracks it for automatic cleanup - */ - const createBlobUrl = (blob: Blob | File): string => { - const url = URL.createObjectURL(blob); - blobUrlsRef.current.add(url); - return url; - }; - - /** - * Revokes a specific blob URL and removes it from tracking - */ - const revokeBlobUrl = (url: string): void => { - if (blobUrlsRef.current.has(url)) { - URL.revokeObjectURL(url); - blobUrlsRef.current.delete(url); - } - }; - - /** - * Revokes all tracked blob URLs - */ - const revokeAll = (): void => { - blobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)); - blobUrlsRef.current.clear(); - }; + useEffect( + () => () => { + for (const url of urlsRef.current) { + URL.revokeObjectURL(url); + } + }, + [], + ); return { - createBlobUrl, - revokeBlobUrl, - revokeAll, + createBlobUrl: (blob: Blob | File): string => { + const url = URL.createObjectURL(blob); + urlsRef.current.add(url); + return url; + }, + revokeBlobUrl: (url: string) => { + if (urlsRef.current.has(url)) { + URL.revokeObjectURL(url); + urlsRef.current.delete(url); + } + }, }; } diff --git a/web/src/components/MemoEditor/hooks/useDebounce.ts b/web/src/components/MemoEditor/hooks/useDebounce.ts deleted file mode 100644 index fe9f91e73..000000000 --- a/web/src/components/MemoEditor/hooks/useDebounce.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useCallback, useEffect, useRef } from "react"; - -/** - * Custom hook for debouncing function calls - * - * @param callback - Function to debounce - * @param delay - Delay in milliseconds before invoking the callback - * @returns Debounced version of the callback function - * - * @example - * ```tsx - * const debouncedSearch = useDebounce((query: string) => { - * performSearch(query); - * }, 300); - * - * // Call multiple times, only last call executes after 300ms - * debouncedSearch("hello"); - * ``` - */ -export function useDebounce void>(callback: T, delay: number): (...args: Parameters) => void { - const timeoutRef = useRef | null>(null); - const callbackRef = useRef(callback); - - // Keep callback ref up to date - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - - // Clean up timeout on unmount - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - - return useCallback( - (...args: Parameters) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = setTimeout(() => { - callbackRef.current(...args); - }, delay); - }, - [delay], - ); -} diff --git a/web/src/components/MemoEditor/hooks/useDragAndDrop.ts b/web/src/components/MemoEditor/hooks/useDragAndDrop.ts index 8a0e13975..5397bd1c7 100644 --- a/web/src/components/MemoEditor/hooks/useDragAndDrop.ts +++ b/web/src/components/MemoEditor/hooks/useDragAndDrop.ts @@ -1,59 +1,32 @@ import { useState } from "react"; -interface UseDragAndDropOptions { - onDrop: (files: FileList) => void; -} - /** - * Custom hook for handling drag-and-drop file uploads - * Manages drag state and event handlers - * - * @param options - Configuration options - * @returns Drag state and event handlers - * - * @example - * ```tsx - * const { isDragging, dragHandlers } = useDragAndDrop({ - * onDrop: (files) => handleFiles(files), - * }); - * - *
- * Drop files here - *
- * ``` + * Hook for handling drag-and-drop file uploads */ -export function useDragAndDrop({ onDrop }: UseDragAndDropOptions) { +export function useDragAndDrop(onDrop: (files: FileList) => void) { const [isDragging, setIsDragging] = useState(false); - const handleDragOver = (event: React.DragEvent): void => { - if (event.dataTransfer && event.dataTransfer.types.includes("Files")) { - event.preventDefault(); - event.dataTransfer.dropEffect = "copy"; - if (!isDragging) { - setIsDragging(true); - } - } - }; - - const handleDragLeave = (event: React.DragEvent): void => { - event.preventDefault(); - setIsDragging(false); - }; - - const handleDrop = (event: React.DragEvent): void => { - if (event.dataTransfer && event.dataTransfer.files.length > 0) { - event.preventDefault(); - setIsDragging(false); - onDrop(event.dataTransfer.files); - } - }; - return { isDragging, dragHandlers: { - onDragOver: handleDragOver, - onDragLeave: handleDragLeave, - onDrop: handleDrop, + onDragOver: (e: React.DragEvent) => { + if (e.dataTransfer?.types.includes("Files")) { + e.preventDefault(); + e.dataTransfer.dropEffect = "copy"; + setIsDragging(true); + } + }, + onDragLeave: (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + }, + onDrop: (e: React.DragEvent) => { + if (e.dataTransfer?.files.length) { + e.preventDefault(); + setIsDragging(false); + onDrop(e.dataTransfer.files); + } + }, }, }; } diff --git a/web/src/components/MemoEditor/hooks/useFocusMode.ts b/web/src/components/MemoEditor/hooks/useFocusMode.ts index 7b63768d3..05ece711c 100644 --- a/web/src/components/MemoEditor/hooks/useFocusMode.ts +++ b/web/src/components/MemoEditor/hooks/useFocusMode.ts @@ -1,40 +1,13 @@ -import { useCallback, useEffect } from "react"; - -interface UseFocusModeOptions { - isFocusMode: boolean; - onToggle: () => void; -} - -interface UseFocusModeReturn { - toggleFocusMode: () => void; -} +import { useEffect } from "react"; /** - * Custom hook for managing focus mode functionality - * Handles: - * - Body scroll lock when focus mode is active - * - Toggle functionality - * - Cleanup on unmount + * Hook to lock body scroll when focus mode is active */ -export function useFocusMode({ isFocusMode, onToggle }: UseFocusModeOptions): UseFocusModeReturn { - // Lock body scroll when focus mode is active to prevent background scrolling +export function useFocusMode(isFocusMode: boolean): void { useEffect(() => { - if (isFocusMode) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = ""; - } - // Cleanup on unmount + document.body.style.overflow = isFocusMode ? "hidden" : ""; return () => { document.body.style.overflow = ""; }; }, [isFocusMode]); - - const toggleFocusMode = useCallback(() => { - onToggle(); - }, [onToggle]); - - return { - toggleFocusMode, - }; } diff --git a/web/src/components/MemoEditor/hooks/useLinkMemo.tsx b/web/src/components/MemoEditor/hooks/useLinkMemo.ts similarity index 74% rename from web/src/components/MemoEditor/hooks/useLinkMemo.tsx rename to web/src/components/MemoEditor/hooks/useLinkMemo.ts index 25753ab12..9f641951b 100644 --- a/web/src/components/MemoEditor/hooks/useLinkMemo.tsx +++ b/web/src/components/MemoEditor/hooks/useLinkMemo.ts @@ -59,39 +59,11 @@ export const useLinkMemo = ({ isOpen, currentMemoName, existingRelations, onAddR onAddRelation(relation); }; - const getHighlightedContent = (content: string): React.ReactNode => { - if (!searchText) return content; - - const index = content.toLowerCase().indexOf(searchText.toLowerCase()); - if (index === -1) { - return content; - } - - let before = content.slice(0, index); - if (before.length > 20) { - before = "..." + before.slice(before.length - 20); - } - const highlighted = content.slice(index, index + searchText.length); - let after = content.slice(index + searchText.length); - if (after.length > 20) { - after = after.slice(0, 20) + "..."; - } - - return ( - <> - {before} - {highlighted} - {after} - - ); - }; - return { searchText, setSearchText, isFetching, filteredMemos, addMemoRelation, - getHighlightedContent, }; }; diff --git a/web/src/components/MemoEditor/hooks/useMemoEditorKeyboard.ts b/web/src/components/MemoEditor/hooks/useMemoEditorKeyboard.ts index 8814977d8..2d346be0f 100644 --- a/web/src/components/MemoEditor/hooks/useMemoEditorKeyboard.ts +++ b/web/src/components/MemoEditor/hooks/useMemoEditorKeyboard.ts @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { TAB_SPACE_WIDTH } from "@/helpers/consts"; import { FOCUS_MODE_EXIT_KEY, FOCUS_MODE_TOGGLE_KEY } from "../constants"; import type { EditorRefActions } from "../Editor"; -import { handleEditorKeydownWithMarkdownShortcuts } from "../Editor/markdownShortcuts"; +import { handleMarkdownShortcuts } from "../Editor/markdownShortcuts"; export interface UseMemoEditorKeyboardOptions { editorRef: React.RefObject; @@ -48,7 +48,7 @@ export const useMemoEditorKeyboard = (options: UseMemoEditorKeyboardOptions) => onSave(); return; } - handleEditorKeydownWithMarkdownShortcuts(event, editorRef.current); + handleMarkdownShortcuts(event, editorRef.current); } // Tab handling diff --git a/web/src/components/MemoEditor/hooks/useMemoEditorState.ts b/web/src/components/MemoEditor/hooks/useMemoEditorState.ts index 9eaa9b2cc..e34c4a7b7 100644 --- a/web/src/components/MemoEditor/hooks/useMemoEditorState.ts +++ b/web/src/components/MemoEditor/hooks/useMemoEditorState.ts @@ -1,11 +1,9 @@ -import { useCallback, useState } from "react"; +import { useState } from "react"; import type { Attachment } from "@/types/proto/api/v1/attachment_service"; import type { Location, MemoRelation } from "@/types/proto/api/v1/memo_service"; import { Visibility } from "@/types/proto/api/v1/memo_service"; -import type { MemoEditorState } from "../types/memo-editor"; -export interface UseMemoEditorStateReturn { - state: MemoEditorState; +interface MemoEditorState { memoVisibility: Visibility; attachmentList: Attachment[]; relationList: MemoRelation[]; @@ -15,25 +13,12 @@ export interface UseMemoEditorStateReturn { isRequesting: boolean; isComposing: boolean; isDraggingFile: boolean; - - setMemoVisibility: (visibility: Visibility) => void; - setAttachmentList: (attachments: Attachment[]) => void; - setRelationList: (relations: MemoRelation[]) => void; - setLocation: (location: Location | undefined) => void; - setIsFocusMode: (isFocusMode: boolean) => void; - toggleFocusMode: () => void; - setUploadingAttachment: (isUploading: boolean) => void; - setRequesting: (isRequesting: boolean) => void; - setComposing: (isComposing: boolean) => void; - setDraggingFile: (isDragging: boolean) => void; - resetState: () => void; } /** * Hook for managing MemoEditor state - * Centralizes all state management and provides clean setters */ -export const useMemoEditorState = (initialVisibility: Visibility = Visibility.PRIVATE): UseMemoEditorStateReturn => { +export const useMemoEditorState = (initialVisibility: Visibility = Visibility.PRIVATE) => { const [state, setState] = useState({ memoVisibility: initialVisibility, isFocusMode: false, @@ -46,79 +31,29 @@ export const useMemoEditorState = (initialVisibility: Visibility = Visibility.PR isDraggingFile: false, }); - const setMemoVisibility = useCallback((visibility: Visibility) => { - setState((prev) => ({ ...prev, memoVisibility: visibility })); - }, []); - - const setAttachmentList = useCallback((attachments: Attachment[]) => { - setState((prev) => ({ ...prev, attachmentList: attachments })); - }, []); - - const setRelationList = useCallback((relations: MemoRelation[]) => { - setState((prev) => ({ ...prev, relationList: relations })); - }, []); - - const setLocation = useCallback((location: Location | undefined) => { - setState((prev) => ({ ...prev, location })); - }, []); - - const setIsFocusMode = useCallback((isFocusMode: boolean) => { - setState((prev) => ({ ...prev, isFocusMode })); - }, []); - - const toggleFocusMode = useCallback(() => { - setState((prev) => ({ ...prev, isFocusMode: !prev.isFocusMode })); - }, []); - - const setUploadingAttachment = useCallback((isUploading: boolean) => { - setState((prev) => ({ ...prev, isUploadingAttachment: isUploading })); - }, []); - - const setRequesting = useCallback((isRequesting: boolean) => { - setState((prev) => ({ ...prev, isRequesting })); - }, []); - - const setComposing = useCallback((isComposing: boolean) => { - setState((prev) => ({ ...prev, isComposing })); - }, []); - - const setDraggingFile = useCallback((isDragging: boolean) => { - setState((prev) => ({ ...prev, isDraggingFile: isDragging })); - }, []); - - const resetState = useCallback(() => { - setState((prev) => ({ - ...prev, - isRequesting: false, - attachmentList: [], - relationList: [], - location: undefined, - isDraggingFile: false, - })); - }, []); + const update = (key: K, value: MemoEditorState[K]) => { + setState((prev) => ({ ...prev, [key]: value })); + }; return { - state, - memoVisibility: state.memoVisibility, - attachmentList: state.attachmentList, - relationList: state.relationList, - location: state.location, - isFocusMode: state.isFocusMode, - isUploadingAttachment: state.isUploadingAttachment, - isRequesting: state.isRequesting, - isComposing: state.isComposing, - isDraggingFile: state.isDraggingFile, - - setMemoVisibility, - setAttachmentList, - setRelationList, - setLocation, - setIsFocusMode, - toggleFocusMode, - setUploadingAttachment, - setRequesting, - setComposing, - setDraggingFile, - resetState, + ...state, + setMemoVisibility: (v: Visibility) => update("memoVisibility", v), + setAttachmentList: (v: Attachment[]) => update("attachmentList", v), + setRelationList: (v: MemoRelation[]) => update("relationList", v), + setLocation: (v: Location | undefined) => update("location", v), + toggleFocusMode: () => setState((prev) => ({ ...prev, isFocusMode: !prev.isFocusMode })), + setUploadingAttachment: (v: boolean) => update("isUploadingAttachment", v), + setRequesting: (v: boolean) => update("isRequesting", v), + setComposing: (v: boolean) => update("isComposing", v), + setDraggingFile: (v: boolean) => update("isDraggingFile", v), + resetState: () => + setState((prev) => ({ + ...prev, + isRequesting: false, + attachmentList: [], + relationList: [], + location: undefined, + isDraggingFile: false, + })), }; }; diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 4d017118b..f6e57b68e 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -18,7 +18,6 @@ import { ErrorBoundary, FocusModeExitButton, FocusModeOverlay } from "./componen import { FOCUS_MODE_STYLES, LOCALSTORAGE_DEBOUNCE_DELAY } from "./constants"; import Editor, { type EditorRefActions } from "./Editor"; import { - useDebounce, useDragAndDrop, useFocusMode, useLocalFileManager, @@ -31,12 +30,19 @@ import { import InsertMenu from "./Toolbar/InsertMenu"; import VisibilitySelector from "./Toolbar/VisibilitySelector"; import { MemoEditorContext } from "./types"; -import type { MemoEditorProps } from "./types/memo-editor"; -// Re-export for backward compatibility -export type { MemoEditorProps as Props }; +export interface Props { + className?: string; + cacheKey?: string; + placeholder?: string; + memoName?: string; + parentMemoName?: string; + autoFocus?: boolean; + onConfirm?: (memoName: string) => void; + onCancel?: () => void; +} -const MemoEditor = observer((props: MemoEditorProps) => { +const MemoEditor = observer((props: Props) => { const { className, cacheKey, memoName, parentMemoName, autoFocus, onConfirm, onCancel } = props; const t = useTranslate(); const { i18n } = useTranslation(); @@ -160,29 +166,31 @@ const MemoEditor = observer((props: MemoEditorProps) => { }); // Focus mode management with body scroll lock - useFocusMode({ - isFocusMode, - onToggle: toggleFocusMode, - }); + useFocusMode(isFocusMode); // Drag-and-drop for file uploads - const { isDragging, dragHandlers } = useDragAndDrop({ - onDrop: addFiles, - }); + const { isDragging, dragHandlers } = useDragAndDrop(addFiles); // Sync drag state with component state useEffect(() => { setDraggingFile(isDragging); }, [isDragging, setDraggingFile]); - // Debounced cache setter to avoid writing to localStorage on every keystroke - const saveContentToCache = useDebounce((content: string) => { - if (content !== "") { - setContentCache(content); - } else { - localStorage.removeItem(contentCacheKey); - } - }, LOCALSTORAGE_DEBOUNCE_DELAY); + // Debounced cache setter + const cacheTimeoutRef = useRef>(); + const saveContentToCache = useCallback( + (content: string) => { + clearTimeout(cacheTimeoutRef.current); + cacheTimeoutRef.current = setTimeout(() => { + if (content !== "") { + setContentCache(content); + } else { + localStorage.removeItem(contentCacheKey); + } + }, LOCALSTORAGE_DEBOUNCE_DELAY); + }, + [contentCacheKey, setContentCache], + ); // Compute reference relations const referenceRelations = useMemo(() => { diff --git a/web/src/components/MemoEditor/types/command.ts b/web/src/components/MemoEditor/types/command.ts deleted file mode 100644 index aab2a38ef..000000000 --- a/web/src/components/MemoEditor/types/command.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Command = { - name: string; - run: () => string; - cursorOffset?: number; -}; diff --git a/web/src/components/MemoEditor/types/index.ts b/web/src/components/MemoEditor/types/index.ts index 9e2f38b24..8474d38d7 100644 --- a/web/src/components/MemoEditor/types/index.ts +++ b/web/src/components/MemoEditor/types/index.ts @@ -1,5 +1,3 @@ // MemoEditor type exports -export type { Command } from "./command"; export { MemoEditorContext, type MemoEditorContextValue } from "./context"; -export type { LinkMemoState, LocationState } from "./insert-menu"; -export type { EditorConfig, MemoEditorProps, MemoEditorState } from "./memo-editor"; +export type { LocationState } from "./insert-menu"; diff --git a/web/src/components/MemoEditor/types/insert-menu.ts b/web/src/components/MemoEditor/types/insert-menu.ts index afd9c36d3..041e3d154 100644 --- a/web/src/components/MemoEditor/types/insert-menu.ts +++ b/web/src/components/MemoEditor/types/insert-menu.ts @@ -1,5 +1,4 @@ import { LatLng } from "leaflet"; -import { Memo } from "@/types/proto/api/v1/memo_service"; export interface LocationState { placeholder: string; @@ -7,9 +6,3 @@ export interface LocationState { latInput: string; lngInput: string; } - -export interface LinkMemoState { - searchText: string; - isFetching: boolean; - fetchedMemos: Memo[]; -} diff --git a/web/src/components/MemoEditor/types/memo-editor.ts b/web/src/components/MemoEditor/types/memo-editor.ts deleted file mode 100644 index 6c0d17bd5..000000000 --- a/web/src/components/MemoEditor/types/memo-editor.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Attachment } from "@/types/proto/api/v1/attachment_service"; -import type { Location, MemoRelation, Visibility } from "@/types/proto/api/v1/memo_service"; - -/** - * Props for the MemoEditor component - */ -export interface MemoEditorProps { - /** Optional CSS class name */ - className?: string; - /** Cache key for localStorage persistence */ - cacheKey?: string; - /** Placeholder text for empty editor */ - placeholder?: string; - /** Name of the memo being edited (for edit mode) */ - memoName?: string; - /** Name of parent memo (for comment/reply mode) */ - parentMemoName?: string; - /** Whether to auto-focus the editor on mount */ - autoFocus?: boolean; - /** Callback when memo is saved successfully */ - onConfirm?: (memoName: string) => void; - /** Callback when editing is canceled */ - onCancel?: () => void; -} - -/** - * Internal state for MemoEditor component - */ -export interface MemoEditorState { - /** Visibility level of the memo */ - memoVisibility: Visibility; - /** List of attachments */ - attachmentList: Attachment[]; - /** List of related memos */ - relationList: MemoRelation[]; - /** Geographic location */ - location: Location | undefined; - /** Whether attachments are currently being uploaded */ - isUploadingAttachment: boolean; - /** Whether save/update request is in progress */ - isRequesting: boolean; - /** Whether IME composition is active (for Asian languages) */ - isComposing: boolean; - /** Whether files are being dragged over the editor */ - isDraggingFile: boolean; - /** Whether Focus Mode is enabled */ - isFocusMode: boolean; -} - -/** - * Configuration for the Editor sub-component - */ -export interface EditorConfig { - className: string; - initialContent: string; - placeholder: string; - onContentChange: (content: string) => void; - onPaste: (event: React.ClipboardEvent) => void; - isFocusMode: boolean; - isInIME: boolean; - onCompositionStart: () => void; - onCompositionEnd: () => void; -} diff --git a/web/src/helpers/consts.ts b/web/src/helpers/consts.ts index 72286def6..13c203dec 100644 --- a/web/src/helpers/consts.ts +++ b/web/src/helpers/consts.ts @@ -1,9 +1,3 @@ -// UNKNOWN_ID is the symbol for unknown id. -export const UNKNOWN_ID = -1; - -// DAILY_TIMESTAMP is the timestamp for a day. -export const DAILY_TIMESTAMP = 3600 * 24 * 1000; - // TAB_SPACE_WIDTH is the default tab space width. export const TAB_SPACE_WIDTH = 2;