From 8a7c9767587ef2aa99d7a23dc01e2aab76ba0056 Mon Sep 17 00:00:00 2001 From: Johnny Date: Mon, 22 Dec 2025 22:54:09 +0800 Subject: [PATCH] refactor: streamline tag sorting and update coordinate handling in MemoEditor components --- .../MemoEditor/Editor/TagSuggestions.tsx | 6 +- .../components/MemoEditor/Editor/index.tsx | 4 +- .../MemoEditor/Toolbar/InsertMenu.tsx | 3 +- .../MemoEditor/Toolbar/VisibilitySelector.tsx | 2 +- .../MemoEditor/components/ErrorBoundary.tsx | 70 ------- .../MemoEditor/components/LocationDialog.tsx | 10 +- .../components/MemoEditor/components/index.ts | 1 - .../MemoEditor/hooks/useLocation.ts | 26 +-- web/src/components/MemoEditor/index.tsx | 198 +++++++++--------- 9 files changed, 116 insertions(+), 204 deletions(-) delete mode 100644 web/src/components/MemoEditor/components/ErrorBoundary.tsx diff --git a/web/src/components/MemoEditor/Editor/TagSuggestions.tsx b/web/src/components/MemoEditor/Editor/TagSuggestions.tsx index 84235505e..97c24e80a 100644 --- a/web/src/components/MemoEditor/Editor/TagSuggestions.tsx +++ b/web/src/components/MemoEditor/Editor/TagSuggestions.tsx @@ -13,11 +13,9 @@ interface TagSuggestionsProps { const TagSuggestions = observer(({ editorRef, editorActions }: TagSuggestionsProps) => { const sortedTags = useMemo(() => { - const tags = Object.entries(userStore.state.tagCount) - .sort((a, b) => b[1] - a[1]) // Sort by usage count (descending) + return Object.entries(userStore.state.tagCount) + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) .map(([tag]) => tag); - // Secondary sort by name for stable ordering - return tags.sort((a, b) => (userStore.state.tagCount[a] === userStore.state.tagCount[b] ? a.localeCompare(b) : 0)); }, [userStore.state.tagCount]); const { position, suggestions, selectedIndex, isVisible, handleItemSelect } = useSuggestions({ diff --git a/web/src/components/MemoEditor/Editor/index.tsx b/web/src/components/MemoEditor/Editor/index.tsx index 8d9bd0248..5e9ec6bda 100644 --- a/web/src/components/MemoEditor/Editor/index.tsx +++ b/web/src/components/MemoEditor/Editor/index.tsx @@ -8,8 +8,8 @@ import { useListCompletion } from "./useListCompletion"; export interface EditorRefActions { getEditor: () => HTMLTextAreaElement | null; - focus: FunctionType; - scrollToCursor: FunctionType; + focus: () => void; + scrollToCursor: () => void; insertText: (text: string, prefix?: string, suffix?: string) => void; removeText: (start: number, length: number) => void; setContent: (text: string) => void; diff --git a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx index bc893c296..19a0e0101 100644 --- a/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx +++ b/web/src/components/MemoEditor/Toolbar/InsertMenu.tsx @@ -214,8 +214,7 @@ const InsertMenu = observer((props: Props) => { state={location.state} locationInitialized={location.locationInitialized} onPositionChange={handlePositionChange} - onLatChange={location.handleLatChange} - onLngChange={location.handleLngChange} + onUpdateCoordinate={location.updateCoordinate} onPlaceholderChange={location.setPlaceholder} onCancel={handleLocationCancel} onConfirm={handleLocationConfirm} diff --git a/web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx b/web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx index 8f8262955..6172ac366 100644 --- a/web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx +++ b/web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx @@ -18,7 +18,7 @@ const VisibilitySelector = (props: Props) => { { value: Visibility.PRIVATE, label: t("memo.visibility.private") }, { value: Visibility.PROTECTED, label: t("memo.visibility.protected") }, { value: Visibility.PUBLIC, label: t("memo.visibility.public") }, - ]; + ] as const; const currentLabel = visibilityOptions.find((option) => option.value === value)?.label || ""; diff --git a/web/src/components/MemoEditor/components/ErrorBoundary.tsx b/web/src/components/MemoEditor/components/ErrorBoundary.tsx deleted file mode 100644 index e8bf69bdc..000000000 --- a/web/src/components/MemoEditor/components/ErrorBoundary.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AlertCircle } from "lucide-react"; -import React from "react"; - -interface Props { - children: React.ReactNode; - fallback?: React.ReactNode; -} - -interface State { - hasError: boolean; - error: Error | null; -} - -class MemoEditorErrorBoundary extends React.Component { - constructor(props: Props) { - super(props); - this.state = { hasError: false, error: null }; - } - - static getDerivedStateFromError(error: Error): State { - // Update state so the next render will show the fallback UI - return { hasError: true, error }; - } - - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - // Log the error to console for debugging - console.error("MemoEditor Error:", error, errorInfo); - // You can also log the error to an error reporting service here - } - - handleReset = () => { - this.setState({ hasError: false, error: null }); - }; - - render() { - if (this.state.hasError) { - // Custom fallback UI - if (this.props.fallback) { - return this.props.fallback; - } - - // Default fallback UI - return ( -
- -

Editor Error

-

- Something went wrong with the memo editor. Please try refreshing the page. -

- {this.state.error && ( -
- Error details -
{this.state.error.toString()}
-
- )} - -
- ); - } - - return this.props.children; - } -} - -export default MemoEditorErrorBoundary; diff --git a/web/src/components/MemoEditor/components/LocationDialog.tsx b/web/src/components/MemoEditor/components/LocationDialog.tsx index a812be061..33e49f757 100644 --- a/web/src/components/MemoEditor/components/LocationDialog.tsx +++ b/web/src/components/MemoEditor/components/LocationDialog.tsx @@ -15,8 +15,7 @@ interface LocationDialogProps { state: LocationState; locationInitialized: boolean; onPositionChange: (position: LatLng) => void; - onLatChange: (value: string) => void; - onLngChange: (value: string) => void; + onUpdateCoordinate: (type: "lat" | "lng", value: string) => void; onPlaceholderChange: (value: string) => void; onCancel: () => void; onConfirm: () => void; @@ -28,8 +27,7 @@ export const LocationDialog = ({ state, locationInitialized, onPositionChange, - onLatChange, - onLngChange, + onUpdateCoordinate, onPlaceholderChange, onCancel, onConfirm, @@ -67,7 +65,7 @@ export const LocationDialog = ({ min="-90" max="90" value={latInput} - onChange={(e) => onLatChange(e.target.value)} + onChange={(e) => onUpdateCoordinate("lat", e.target.value)} className="h-9" /> @@ -83,7 +81,7 @@ export const LocationDialog = ({ min="-180" max="180" value={lngInput} - onChange={(e) => onLngChange(e.target.value)} + onChange={(e) => onUpdateCoordinate("lng", e.target.value)} className="h-9" /> diff --git a/web/src/components/MemoEditor/components/index.ts b/web/src/components/MemoEditor/components/index.ts index 86dc5b2ff..566179215 100644 --- a/web/src/components/MemoEditor/components/index.ts +++ b/web/src/components/MemoEditor/components/index.ts @@ -1,5 +1,4 @@ // UI components for MemoEditor -export { default as ErrorBoundary } from "./ErrorBoundary"; export { FocusModeExitButton, FocusModeOverlay } from "./FocusModeOverlay"; export { LinkMemoDialog } from "./LinkMemoDialog"; export { LocationDialog } from "./LocationDialog"; diff --git a/web/src/components/MemoEditor/hooks/useLocation.ts b/web/src/components/MemoEditor/hooks/useLocation.ts index 016088246..334df849a 100644 --- a/web/src/components/MemoEditor/hooks/useLocation.ts +++ b/web/src/components/MemoEditor/hooks/useLocation.ts @@ -23,25 +23,16 @@ export const useLocation = (initialLocation?: Location) => { }; const handlePositionChange = (position: LatLng) => { - if (!locationInitialized) { - setLocationInitialized(true); - } + if (!locationInitialized) setLocationInitialized(true); updatePosition(position); }; - const handleLatChange = (value: string) => { - setState((prev) => ({ ...prev, latInput: value })); - const lat = parseFloat(value); - if (!isNaN(lat) && lat >= -90 && lat <= 90 && state.position) { - updatePosition(new LatLng(lat, state.position.lng)); - } - }; - - const handleLngChange = (value: string) => { - setState((prev) => ({ ...prev, lngInput: value })); - const lng = parseFloat(value); - if (!isNaN(lng) && lng >= -180 && lng <= 180 && state.position) { - updatePosition(new LatLng(state.position.lat, lng)); + const updateCoordinate = (type: "lat" | "lng", value: string) => { + setState((prev) => ({ ...prev, [type === "lat" ? "latInput" : "lngInput"]: value })); + const num = parseFloat(value); + const isValid = type === "lat" ? !isNaN(num) && num >= -90 && num <= 90 : !isNaN(num) && num >= -180 && num <= 180; + if (isValid && state.position) { + updatePosition(type === "lat" ? new LatLng(num, state.position.lng) : new LatLng(state.position.lat, num)); } }; @@ -74,8 +65,7 @@ export const useLocation = (initialLocation?: Location) => { state, locationInitialized, handlePositionChange, - handleLatChange, - handleLngChange, + updateCoordinate, setPlaceholder, reset, getLocation, diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 7cf67c9be..ff75df9f3 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -14,7 +14,7 @@ import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb"; import { useTranslate } from "@/utils/i18n"; import DateTimeInput from "../DateTimeInput"; import { AttachmentList, LocationDisplay, RelationList } from "../memo-metadata"; -import { ErrorBoundary, FocusModeExitButton, FocusModeOverlay } from "./components"; +import { FocusModeExitButton, FocusModeOverlay } from "./components"; import { FOCUS_MODE_STYLES, LOCALSTORAGE_DEBOUNCE_DELAY } from "./constants"; import Editor, { type EditorRefActions } from "./Editor"; import { @@ -224,113 +224,111 @@ const MemoEditor = observer((props: Props) => { const allowSave = (hasContent || attachmentList.length > 0 || localFiles.length > 0) && !isUploadingAttachment && !isRequesting; return ( - - addFiles(Array.from(files.map((f) => f.file))), - removeLocalFile: removeFile, - localFiles, - setRelationList, - memoName, - }} + addFiles(Array.from(files.map((f) => f.file))), + removeLocalFile: removeFile, + localFiles, + setRelationList, + memoName, + }} + > + {/* Focus Mode Backdrop */} + + +
- {/* Focus Mode Backdrop */} - + {/* Focus Mode Exit Button */} + -
- {/* Focus Mode Exit Button */} - - - - setLocation(undefined)} /> - {/* Show attachments and pending files together */} - - -
e.stopPropagation()}> -
- -
-
- -
- {props.onCancel && ( - - )} - -
+ )} +
+
- {/* Show memo metadata if memoName is provided */} - {memoName && ( -
-
- {!isEqual(createTime, updateTime) && updateTime && ( - <> - Updated - - - )} - {createTime && ( - <> - Created - - - )} - ID - -
+ {/* Show memo metadata if memoName is provided */} + {memoName && ( +
+
+ {!isEqual(createTime, updateTime) && updateTime && ( + <> + Updated + + + )} + {createTime && ( + <> + Created + + + )} + ID +
- )} - - +
+ )} + ); });