Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
792d58b7
Commit
792d58b7
authored
Dec 28, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: consolidate and update type definitions across MemoEditor components
parent
40585607
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
147 additions
and
187 deletions
+147
-187
SlashCommands.tsx
web/src/components/MemoEditor/Editor/SlashCommands.tsx
+1
-8
TagSuggestions.tsx
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
+1
-6
index.tsx
web/src/components/MemoEditor/Editor/index.tsx
+2
-13
InsertMenu.tsx
web/src/components/MemoEditor/Toolbar/InsertMenu.tsx
+12
-21
VisibilitySelector.tsx
web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx
+2
-7
EditorContent.tsx
web/src/components/MemoEditor/components/EditorContent.tsx
+1
-5
EditorMetadata.tsx
web/src/components/MemoEditor/components/EditorMetadata.tsx
+2
-1
EditorToolbar.tsx
web/src/components/MemoEditor/components/EditorToolbar.tsx
+3
-6
FocusModeOverlay.tsx
...src/components/MemoEditor/components/FocusModeOverlay.tsx
+1
-11
LinkMemoDialog.tsx
web/src/components/MemoEditor/components/LinkMemoDialog.tsx
+1
-11
LocationDialog.tsx
web/src/components/MemoEditor/components/LocationDialog.tsx
+1
-14
index.ts
web/src/components/MemoEditor/hooks/index.ts
+0
-1
useDragAndDrop.ts
web/src/components/MemoEditor/hooks/useDragAndDrop.ts
+0
-8
useLocalFileManager.ts
web/src/components/MemoEditor/hooks/useLocalFileManager.ts
+0
-39
index.tsx
web/src/components/MemoEditor/index.tsx
+5
-36
components.ts
web/src/components/MemoEditor/types/components.ts
+99
-0
index.ts
web/src/components/MemoEditor/types/index.ts
+16
-0
No files found.
web/src/components/MemoEditor/Editor/SlashCommands.tsx
View file @
792d58b7
import
type
{
EditorRefActions
}
from
"."
;
import
type
{
SlashCommandsProps
}
from
"../types"
;
import
type
{
Command
}
from
"./commands"
;
import
{
SuggestionsPopup
}
from
"./SuggestionsPopup"
;
import
{
SuggestionsPopup
}
from
"./SuggestionsPopup"
;
import
{
useSuggestions
}
from
"./useSuggestions"
;
import
{
useSuggestions
}
from
"./useSuggestions"
;
interface
SlashCommandsProps
{
editorRef
:
React
.
RefObject
<
HTMLTextAreaElement
>
;
editorActions
:
React
.
ForwardedRef
<
EditorRefActions
>
;
commands
:
Command
[];
}
const
SlashCommands
=
({
editorRef
,
editorActions
,
commands
}:
SlashCommandsProps
)
=>
{
const
SlashCommands
=
({
editorRef
,
editorActions
,
commands
}:
SlashCommandsProps
)
=>
{
const
{
position
,
suggestions
,
selectedIndex
,
isVisible
,
handleItemSelect
}
=
useSuggestions
({
const
{
position
,
suggestions
,
selectedIndex
,
isVisible
,
handleItemSelect
}
=
useSuggestions
({
editorRef
,
editorRef
,
...
...
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
View file @
792d58b7
...
@@ -3,15 +3,10 @@ import { matchPath } from "react-router-dom";
...
@@ -3,15 +3,10 @@ import { matchPath } from "react-router-dom";
import
OverflowTip
from
"@/components/kit/OverflowTip"
;
import
OverflowTip
from
"@/components/kit/OverflowTip"
;
import
{
useTagCounts
}
from
"@/hooks/useUserQueries"
;
import
{
useTagCounts
}
from
"@/hooks/useUserQueries"
;
import
{
Routes
}
from
"@/router"
;
import
{
Routes
}
from
"@/router"
;
import
type
{
EditorRefActions
}
from
".
"
;
import
type
{
TagSuggestionsProps
}
from
"../types
"
;
import
{
SuggestionsPopup
}
from
"./SuggestionsPopup"
;
import
{
SuggestionsPopup
}
from
"./SuggestionsPopup"
;
import
{
useSuggestions
}
from
"./useSuggestions"
;
import
{
useSuggestions
}
from
"./useSuggestions"
;
interface
TagSuggestionsProps
{
editorRef
:
React
.
RefObject
<
HTMLTextAreaElement
>
;
editorActions
:
React
.
ForwardedRef
<
EditorRefActions
>
;
}
export
default
function
TagSuggestions
({
editorRef
,
editorActions
}:
TagSuggestionsProps
)
{
export
default
function
TagSuggestions
({
editorRef
,
editorActions
}:
TagSuggestionsProps
)
{
// On explore page, show all users' tags; otherwise show current user's tags
// On explore page, show all users' tags; otherwise show current user's tags
const
isExplorePage
=
Boolean
(
matchPath
(
Routes
.
EXPLORE
,
window
.
location
.
pathname
));
const
isExplorePage
=
Boolean
(
matchPath
(
Routes
.
EXPLORE
,
window
.
location
.
pathname
));
...
...
web/src/components/MemoEditor/Editor/index.tsx
View file @
792d58b7
import
{
forwardRef
,
useCallback
,
useEffect
,
useImperativeHandle
,
useMemo
,
useRef
}
from
"react"
;
import
{
forwardRef
,
useCallback
,
useEffect
,
useImperativeHandle
,
useMemo
,
useRef
}
from
"react"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
EDITOR_HEIGHT
}
from
"../constants"
;
import
{
EDITOR_HEIGHT
}
from
"../constants"
;
import
type
{
EditorProps
}
from
"../types"
;
import
{
editorCommands
}
from
"./commands"
;
import
{
editorCommands
}
from
"./commands"
;
import
SlashCommands
from
"./SlashCommands"
;
import
SlashCommands
from
"./SlashCommands"
;
import
TagSuggestions
from
"./TagSuggestions"
;
import
TagSuggestions
from
"./TagSuggestions"
;
...
@@ -22,19 +23,7 @@ export interface EditorRefActions {
...
@@ -22,19 +23,7 @@ export interface EditorRefActions {
setLine
:
(
lineNumber
:
number
,
text
:
string
)
=>
void
;
setLine
:
(
lineNumber
:
number
,
text
:
string
)
=>
void
;
}
}
interface
Props
{
const
Editor
=
forwardRef
(
function
Editor
(
props
:
EditorProps
,
ref
:
React
.
ForwardedRef
<
EditorRefActions
>
)
{
className
:
string
;
initialContent
:
string
;
placeholder
:
string
;
onContentChange
:
(
content
:
string
)
=>
void
;
onPaste
:
(
event
:
React
.
ClipboardEvent
)
=>
void
;
isFocusMode
?:
boolean
;
isInIME
?:
boolean
;
onCompositionStart
?:
()
=>
void
;
onCompositionEnd
?:
()
=>
void
;
}
const
Editor
=
forwardRef
(
function
Editor
(
props
:
Props
,
ref
:
React
.
ForwardedRef
<
EditorRefActions
>
)
{
const
{
const
{
className
,
className
,
initialContent
,
initialContent
,
...
...
web/src/components/MemoEditor/Toolbar/InsertMenu.tsx
View file @
792d58b7
import
{
LatLng
}
from
"leaflet"
;
import
{
LatLng
}
from
"leaflet"
;
import
{
uniqBy
}
from
"lodash-es"
;
import
{
uniqBy
}
from
"lodash-es"
;
import
{
FileIcon
,
LinkIcon
,
LoaderIcon
,
MapPinIcon
,
Maximize2Icon
,
MoreHorizontalIcon
,
PlusIcon
}
from
"lucide-react"
;
import
{
FileIcon
,
LinkIcon
,
LoaderIcon
,
MapPinIcon
,
Maximize2Icon
,
MoreHorizontalIcon
,
PlusIcon
}
from
"lucide-react"
;
import
{
use
Context
,
use
State
}
from
"react"
;
import
{
useState
}
from
"react"
;
import
type
{
LocalFile
}
from
"@/components/memo-metadata"
;
import
type
{
LocalFile
}
from
"@/components/memo-metadata"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
...
@@ -14,24 +14,17 @@ import {
...
@@ -14,24 +14,17 @@ import {
DropdownMenuTrigger
,
DropdownMenuTrigger
,
useDropdownMenuSubHoverDelay
,
useDropdownMenuSubHoverDelay
,
}
from
"@/components/ui/dropdown-menu"
;
}
from
"@/components/ui/dropdown-menu"
;
import
type
{
Location
,
MemoRelation
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
type
{
MemoRelation
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
LinkMemoDialog
,
LocationDialog
}
from
"../components"
;
import
{
LinkMemoDialog
,
LocationDialog
}
from
"../components"
;
import
{
GEOCODING
}
from
"../constants"
;
import
{
GEOCODING
}
from
"../constants"
;
import
{
useFileUpload
,
useLinkMemo
,
useLocation
}
from
"../hooks"
;
import
{
useAbortController
,
useFileUpload
,
useLinkMemo
,
useLocation
}
from
"../hooks"
;
import
{
useAbortController
}
from
"../hooks/useAbortController"
;
import
{
useEditorContext
}
from
"../state"
;
import
{
MemoEditorContext
}
from
"../types"
;
import
type
{
InsertMenuProps
}
from
"../types"
;
interface
Props
{
const
InsertMenu
=
(
props
:
InsertMenuProps
)
=>
{
isUploading
?:
boolean
;
location
?:
Location
;
onLocationChange
:
(
location
?:
Location
)
=>
void
;
onToggleFocusMode
?:
()
=>
void
;
}
const
InsertMenu
=
(
props
:
Props
)
=>
{
const
t
=
useTranslate
();
const
t
=
useTranslate
();
const
context
=
useContext
(
MemoEditorContext
);
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
(
);
const
[
linkDialogOpen
,
setLinkDialogOpen
]
=
useState
(
false
);
const
[
linkDialogOpen
,
setLinkDialogOpen
]
=
useState
(
false
);
const
[
locationDialogOpen
,
setLocationDialogOpen
]
=
useState
(
false
);
const
[
locationDialogOpen
,
setLocationDialogOpen
]
=
useState
(
false
);
...
@@ -46,17 +39,15 @@ const InsertMenu = (props: Props) => {
...
@@ -46,17 +39,15 @@ const InsertMenu = (props: Props) => {
);
);
const
{
fileInputRef
,
selectingFlag
,
handleFileInputChange
,
handleUploadClick
}
=
useFileUpload
((
newFiles
:
LocalFile
[])
=>
{
const
{
fileInputRef
,
selectingFlag
,
handleFileInputChange
,
handleUploadClick
}
=
useFileUpload
((
newFiles
:
LocalFile
[])
=>
{
if
(
context
.
addLocalFiles
)
{
newFiles
.
forEach
((
file
)
=>
dispatch
(
actions
.
addLocalFile
(
file
)));
context
.
addLocalFiles
(
newFiles
);
}
});
});
const
linkMemo
=
useLinkMemo
({
const
linkMemo
=
useLinkMemo
({
isOpen
:
linkDialogOpen
,
isOpen
:
linkDialogOpen
,
currentMemoName
:
context
.
memoName
,
currentMemoName
:
props
.
memoName
,
existingRelations
:
context
.
relationList
,
existingRelations
:
state
.
metadata
.
relations
,
onAddRelation
:
(
relation
:
MemoRelation
)
=>
{
onAddRelation
:
(
relation
:
MemoRelation
)
=>
{
context
.
setRelationList
(
uniqBy
([...
context
.
relationList
,
relation
],
(
r
)
=>
r
.
relatedMemo
?.
name
));
dispatch
(
actions
.
setMetadata
({
relations
:
uniqBy
([...
state
.
metadata
.
relations
,
relation
],
(
r
)
=>
r
.
relatedMemo
?.
name
)
}
));
setLinkDialogOpen
(
false
);
setLinkDialogOpen
(
false
);
},
},
});
});
...
...
web/src/components/MemoEditor/Toolbar/VisibilitySelector.tsx
View file @
792d58b7
...
@@ -3,14 +3,9 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
...
@@ -3,14 +3,9 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
import
VisibilityIcon
from
"@/components/VisibilityIcon"
;
import
VisibilityIcon
from
"@/components/VisibilityIcon"
;
import
{
Visibility
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
Visibility
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
type
{
VisibilitySelectorProps
}
from
"../types"
;
interface
Props
{
const
VisibilitySelector
=
(
props
:
VisibilitySelectorProps
)
=>
{
value
:
Visibility
;
onChange
:
(
visibility
:
Visibility
)
=>
void
;
onOpenChange
?:
(
open
:
boolean
)
=>
void
;
}
const
VisibilitySelector
=
(
props
:
Props
)
=>
{
const
{
value
,
onChange
}
=
props
;
const
{
value
,
onChange
}
=
props
;
const
t
=
useTranslate
();
const
t
=
useTranslate
();
...
...
web/src/components/MemoEditor/components/EditorContent.tsx
View file @
792d58b7
...
@@ -3,11 +3,7 @@ import type { LocalFile } from "@/components/memo-metadata";
...
@@ -3,11 +3,7 @@ import type { LocalFile } from "@/components/memo-metadata";
import
Editor
,
{
type
EditorRefActions
}
from
"../Editor"
;
import
Editor
,
{
type
EditorRefActions
}
from
"../Editor"
;
import
{
useBlobUrls
,
useDragAndDrop
}
from
"../hooks"
;
import
{
useBlobUrls
,
useDragAndDrop
}
from
"../hooks"
;
import
{
useEditorContext
}
from
"../state"
;
import
{
useEditorContext
}
from
"../state"
;
import
type
{
EditorContentProps
}
from
"../types"
;
interface
EditorContentProps
{
placeholder
?:
string
;
autoFocus
?:
boolean
;
}
export
const
EditorContent
=
forwardRef
<
EditorRefActions
,
EditorContentProps
>
(({
placeholder
},
ref
)
=>
{
export
const
EditorContent
=
forwardRef
<
EditorRefActions
,
EditorContentProps
>
(({
placeholder
},
ref
)
=>
{
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
...
...
web/src/components/MemoEditor/components/EditorMetadata.tsx
View file @
792d58b7
import
type
{
FC
}
from
"react"
;
import
type
{
FC
}
from
"react"
;
import
{
AttachmentList
,
LocationDisplay
,
RelationList
}
from
"@/components/memo-metadata"
;
import
{
AttachmentList
,
LocationDisplay
,
RelationList
}
from
"@/components/memo-metadata"
;
import
{
useEditorContext
}
from
"../state"
;
import
{
useEditorContext
}
from
"../state"
;
import
type
{
EditorMetadataProps
}
from
"../types"
;
export
const
EditorMetadata
:
FC
=
()
=>
{
export
const
EditorMetadata
:
FC
<
EditorMetadataProps
>
=
()
=>
{
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
return
(
return
(
...
...
web/src/components/MemoEditor/components/EditorToolbar.tsx
View file @
792d58b7
...
@@ -4,13 +4,9 @@ import { validationService } from "../services";
...
@@ -4,13 +4,9 @@ import { validationService } from "../services";
import
{
useEditorContext
}
from
"../state"
;
import
{
useEditorContext
}
from
"../state"
;
import
InsertMenu
from
"../Toolbar/InsertMenu"
;
import
InsertMenu
from
"../Toolbar/InsertMenu"
;
import
VisibilitySelector
from
"../Toolbar/VisibilitySelector"
;
import
VisibilitySelector
from
"../Toolbar/VisibilitySelector"
;
import
type
{
EditorToolbarProps
}
from
"../types"
;
interface
EditorToolbarProps
{
export
const
EditorToolbar
:
FC
<
EditorToolbarProps
>
=
({
onSave
,
onCancel
,
memoName
})
=>
{
onSave
:
()
=>
void
;
onCancel
?:
()
=>
void
;
}
export
const
EditorToolbar
:
FC
<
EditorToolbarProps
>
=
({
onSave
,
onCancel
})
=>
{
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
const
{
valid
}
=
validationService
.
canSave
(
state
);
const
{
valid
}
=
validationService
.
canSave
(
state
);
...
@@ -36,6 +32,7 @@ export const EditorToolbar: FC<EditorToolbarProps> = ({ onSave, onCancel }) => {
...
@@ -36,6 +32,7 @@ export const EditorToolbar: FC<EditorToolbarProps> = ({ onSave, onCancel }) => {
location=
{
state
.
metadata
.
location
}
location=
{
state
.
metadata
.
location
}
onLocationChange=
{
handleLocationChange
}
onLocationChange=
{
handleLocationChange
}
onToggleFocusMode=
{
handleToggleFocusMode
}
onToggleFocusMode=
{
handleToggleFocusMode
}
memoName=
{
memoName
}
/>
/>
</
div
>
</
div
>
...
...
web/src/components/MemoEditor/components/FocusModeOverlay.tsx
View file @
792d58b7
import
{
Minimize2Icon
}
from
"lucide-react"
;
import
{
Minimize2Icon
}
from
"lucide-react"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
FOCUS_MODE_STYLES
}
from
"../constants"
;
import
{
FOCUS_MODE_STYLES
}
from
"../constants"
;
import
type
{
FocusModeExitButtonProps
,
FocusModeOverlayProps
}
from
"../types"
;
interface
FocusModeOverlayProps
{
isActive
:
boolean
;
onToggle
:
()
=>
void
;
}
export
function
FocusModeOverlay
({
isActive
,
onToggle
}:
FocusModeOverlayProps
)
{
export
function
FocusModeOverlay
({
isActive
,
onToggle
}:
FocusModeOverlayProps
)
{
if
(
!
isActive
)
return
null
;
if
(
!
isActive
)
return
null
;
...
@@ -21,12 +17,6 @@ export function FocusModeOverlay({ isActive, onToggle }: FocusModeOverlayProps)
...
@@ -21,12 +17,6 @@ export function FocusModeOverlay({ isActive, onToggle }: FocusModeOverlayProps)
);
);
}
}
interface
FocusModeExitButtonProps
{
isActive
:
boolean
;
onToggle
:
()
=>
void
;
title
:
string
;
}
export
function
FocusModeExitButton
({
isActive
,
onToggle
,
title
}:
FocusModeExitButtonProps
)
{
export
function
FocusModeExitButton
({
isActive
,
onToggle
,
title
}:
FocusModeExitButtonProps
)
{
if
(
!
isActive
)
return
null
;
if
(
!
isActive
)
return
null
;
...
...
web/src/components/MemoEditor/components/LinkMemoDialog.tsx
View file @
792d58b7
import
{
timestampDate
}
from
"@bufbuild/protobuf/wkt"
;
import
{
timestampDate
}
from
"@bufbuild/protobuf/wkt"
;
import
{
Dialog
,
DialogContent
,
DialogHeader
,
DialogTitle
}
from
"@/components/ui/dialog"
;
import
{
Dialog
,
DialogContent
,
DialogHeader
,
DialogTitle
}
from
"@/components/ui/dialog"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Memo
}
from
"@/types/proto/api/v1/memo_service_pb"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
type
{
LinkMemoDialogProps
}
from
"../types"
;
function
highlightSearchText
(
content
:
string
,
searchText
:
string
):
React
.
ReactNode
{
function
highlightSearchText
(
content
:
string
,
searchText
:
string
):
React
.
ReactNode
{
if
(
!
searchText
)
return
content
;
if
(
!
searchText
)
return
content
;
...
@@ -29,16 +29,6 @@ function highlightSearchText(content: string, searchText: string): React.ReactNo
...
@@ -29,16 +29,6 @@ function highlightSearchText(content: string, searchText: string): React.ReactNo
);
);
}
}
interface
LinkMemoDialogProps
{
open
:
boolean
;
onOpenChange
:
(
open
:
boolean
)
=>
void
;
searchText
:
string
;
onSearchChange
:
(
text
:
string
)
=>
void
;
filteredMemos
:
Memo
[];
isFetching
:
boolean
;
onSelectMemo
:
(
memo
:
Memo
)
=>
void
;
}
export
const
LinkMemoDialog
=
({
export
const
LinkMemoDialog
=
({
open
,
open
,
onOpenChange
,
onOpenChange
,
...
...
web/src/components/MemoEditor/components/LocationDialog.tsx
View file @
792d58b7
import
{
LatLng
}
from
"leaflet"
;
import
LeafletMap
from
"@/components/LeafletMap"
;
import
LeafletMap
from
"@/components/LeafletMap"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Dialog
,
DialogClose
,
DialogContent
,
DialogDescription
,
DialogTitle
}
from
"@/components/ui/dialog"
;
import
{
Dialog
,
DialogClose
,
DialogContent
,
DialogDescription
,
DialogTitle
}
from
"@/components/ui/dialog"
;
...
@@ -7,19 +6,7 @@ import { Label } from "@/components/ui/label";
...
@@ -7,19 +6,7 @@ import { Label } from "@/components/ui/label";
import
{
Textarea
}
from
"@/components/ui/textarea"
;
import
{
Textarea
}
from
"@/components/ui/textarea"
;
import
{
VisuallyHidden
}
from
"@/components/ui/visually-hidden"
;
import
{
VisuallyHidden
}
from
"@/components/ui/visually-hidden"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
LocationState
}
from
"../types/insert-menu"
;
import
type
{
LocationDialogProps
}
from
"../types"
;
interface
LocationDialogProps
{
open
:
boolean
;
onOpenChange
:
(
open
:
boolean
)
=>
void
;
state
:
LocationState
;
locationInitialized
:
boolean
;
onPositionChange
:
(
position
:
LatLng
)
=>
void
;
onUpdateCoordinate
:
(
type
:
"lat"
|
"lng"
,
value
:
string
)
=>
void
;
onPlaceholderChange
:
(
value
:
string
)
=>
void
;
onCancel
:
()
=>
void
;
onConfirm
:
()
=>
void
;
}
export
const
LocationDialog
=
({
export
const
LocationDialog
=
({
open
,
open
,
...
...
web/src/components/MemoEditor/hooks/index.ts
View file @
792d58b7
...
@@ -7,6 +7,5 @@ export { useFileUpload } from "./useFileUpload";
...
@@ -7,6 +7,5 @@ export { useFileUpload } from "./useFileUpload";
export
{
useFocusMode
}
from
"./useFocusMode"
;
export
{
useFocusMode
}
from
"./useFocusMode"
;
export
{
useKeyboard
}
from
"./useKeyboard"
;
export
{
useKeyboard
}
from
"./useKeyboard"
;
export
{
useLinkMemo
}
from
"./useLinkMemo"
;
export
{
useLinkMemo
}
from
"./useLinkMemo"
;
export
{
useLocalFileManager
}
from
"./useLocalFileManager"
;
export
{
useLocation
}
from
"./useLocation"
;
export
{
useLocation
}
from
"./useLocation"
;
export
{
useMemoInit
}
from
"./useMemoInit"
;
export
{
useMemoInit
}
from
"./useMemoInit"
;
web/src/components/MemoEditor/hooks/useDragAndDrop.ts
View file @
792d58b7
import
{
useState
}
from
"react"
;
export
function
useDragAndDrop
(
onDrop
:
(
files
:
FileList
)
=>
void
)
{
export
function
useDragAndDrop
(
onDrop
:
(
files
:
FileList
)
=>
void
)
{
const
[
isDragging
,
setIsDragging
]
=
useState
(
false
);
return
{
return
{
isDragging
,
dragHandlers
:
{
dragHandlers
:
{
onDragOver
:
(
e
:
React
.
DragEvent
)
=>
{
onDragOver
:
(
e
:
React
.
DragEvent
)
=>
{
if
(
e
.
dataTransfer
?.
types
.
includes
(
"Files"
))
{
if
(
e
.
dataTransfer
?.
types
.
includes
(
"Files"
))
{
e
.
preventDefault
();
e
.
preventDefault
();
e
.
dataTransfer
.
dropEffect
=
"copy"
;
e
.
dataTransfer
.
dropEffect
=
"copy"
;
setIsDragging
(
true
);
}
}
},
},
onDragLeave
:
(
e
:
React
.
DragEvent
)
=>
{
onDragLeave
:
(
e
:
React
.
DragEvent
)
=>
{
e
.
preventDefault
();
e
.
preventDefault
();
setIsDragging
(
false
);
},
},
onDrop
:
(
e
:
React
.
DragEvent
)
=>
{
onDrop
:
(
e
:
React
.
DragEvent
)
=>
{
if
(
e
.
dataTransfer
?.
files
.
length
)
{
if
(
e
.
dataTransfer
?.
files
.
length
)
{
e
.
preventDefault
();
e
.
preventDefault
();
setIsDragging
(
false
);
onDrop
(
e
.
dataTransfer
.
files
);
onDrop
(
e
.
dataTransfer
.
files
);
}
}
},
},
...
...
web/src/components/MemoEditor/hooks/useLocalFileManager.ts
deleted
100644 → 0
View file @
40585607
import
{
useState
}
from
"react"
;
import
type
{
LocalFile
}
from
"@/components/memo-metadata"
;
import
{
useBlobUrls
}
from
"./useBlobUrls"
;
export
function
useLocalFileManager
()
{
const
[
localFiles
,
setLocalFiles
]
=
useState
<
LocalFile
[]
>
([]);
const
{
createBlobUrl
,
revokeBlobUrl
}
=
useBlobUrls
();
const
addFiles
=
(
files
:
FileList
|
File
[]):
void
=>
{
const
fileArray
=
Array
.
from
(
files
);
const
newLocalFiles
:
LocalFile
[]
=
fileArray
.
map
((
file
)
=>
({
file
,
previewUrl
:
createBlobUrl
(
file
),
}));
setLocalFiles
((
prev
)
=>
[...
prev
,
...
newLocalFiles
]);
};
const
removeFile
=
(
previewUrl
:
string
):
void
=>
{
setLocalFiles
((
prev
)
=>
{
const
toRemove
=
prev
.
find
((
f
)
=>
f
.
previewUrl
===
previewUrl
);
if
(
toRemove
)
{
revokeBlobUrl
(
toRemove
.
previewUrl
);
}
return
prev
.
filter
((
f
)
=>
f
.
previewUrl
!==
previewUrl
);
});
};
const
clearFiles
=
():
void
=>
{
localFiles
.
forEach
(({
previewUrl
})
=>
revokeBlobUrl
(
previewUrl
));
setLocalFiles
([]);
};
return
{
localFiles
,
addFiles
,
removeFile
,
clearFiles
,
};
}
web/src/components/MemoEditor/index.tsx
View file @
792d58b7
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
use
Memo
,
use
Ref
}
from
"react"
;
import
{
useRef
}
from
"react"
;
import
{
toast
}
from
"react-hot-toast"
;
import
{
toast
}
from
"react-hot-toast"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
memoKeys
}
from
"@/hooks/useMemoQueries"
;
import
{
memoKeys
}
from
"@/hooks/useMemoQueries"
;
...
@@ -13,18 +13,7 @@ import type { EditorRefActions } from "./Editor";
...
@@ -13,18 +13,7 @@ import type { EditorRefActions } from "./Editor";
import
{
useAutoSave
,
useFocusMode
,
useKeyboard
,
useMemoInit
}
from
"./hooks"
;
import
{
useAutoSave
,
useFocusMode
,
useKeyboard
,
useMemoInit
}
from
"./hooks"
;
import
{
cacheService
,
errorService
,
memoService
,
validationService
}
from
"./services"
;
import
{
cacheService
,
errorService
,
memoService
,
validationService
}
from
"./services"
;
import
{
EditorProvider
,
useEditorContext
}
from
"./state"
;
import
{
EditorProvider
,
useEditorContext
}
from
"./state"
;
import
{
MemoEditorContext
}
from
"./types"
;
import
type
{
MemoEditorProps
}
from
"./types"
;
export
interface
MemoEditorProps
{
className
?:
string
;
cacheKey
?:
string
;
placeholder
?:
string
;
memoName
?:
string
;
parentMemoName
?:
string
;
autoFocus
?:
boolean
;
onConfirm
?:
(
memoName
:
string
)
=>
void
;
onCancel
?:
()
=>
void
;
}
const
MemoEditor
=
(
props
:
MemoEditorProps
)
=>
{
const
MemoEditor
=
(
props
:
MemoEditorProps
)
=>
{
const
{
className
,
cacheKey
,
memoName
,
parentMemoName
,
autoFocus
,
placeholder
,
onConfirm
,
onCancel
}
=
props
;
const
{
className
,
cacheKey
,
memoName
,
parentMemoName
,
autoFocus
,
placeholder
,
onConfirm
,
onCancel
}
=
props
;
...
@@ -61,26 +50,6 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
...
@@ -61,26 +50,6 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
const
editorRef
=
useRef
<
EditorRefActions
>
(
null
);
const
editorRef
=
useRef
<
EditorRefActions
>
(
null
);
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
const
{
state
,
actions
,
dispatch
}
=
useEditorContext
();
// Bridge for old MemoEditorContext (used by InsertMenu and other components)
const
legacyContextValue
=
useMemo
(
()
=>
({
attachmentList
:
state
.
metadata
.
attachments
,
relationList
:
state
.
metadata
.
relations
,
setAttachmentList
:
(
attachments
:
typeof
state
.
metadata
.
attachments
)
=>
dispatch
(
actions
.
setMetadata
({
attachments
})),
setRelationList
:
(
relations
:
typeof
state
.
metadata
.
relations
)
=>
dispatch
(
actions
.
setMetadata
({
relations
})),
memoName
,
addLocalFiles
:
(
files
:
typeof
state
.
localFiles
)
=>
{
files
.
forEach
((
file
)
=>
{
dispatch
(
actions
.
addLocalFile
(
file
));
});
},
removeLocalFile
:
(
previewUrl
:
string
)
=>
dispatch
(
actions
.
removeLocalFile
(
previewUrl
)),
localFiles
:
state
.
localFiles
,
}),
[
state
.
metadata
.
attachments
,
state
.
metadata
.
relations
,
state
.
localFiles
,
memoName
,
actions
,
dispatch
],
);
// Initialize editor (load memo or cache)
useMemoInit
(
editorRef
,
memoName
,
cacheKey
,
currentUser
?.
name
??
""
,
autoFocus
);
useMemoInit
(
editorRef
,
memoName
,
cacheKey
,
currentUser
?.
name
??
""
,
autoFocus
);
// Auto-save content to localStorage
// Auto-save content to localStorage
...
@@ -149,7 +118,7 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
...
@@ -149,7 +118,7 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
}
}
return
(
return
(
<
MemoEditorContext
.
Provider
value=
{
legacyContextValue
}
>
<>
<
FocusModeOverlay
isActive=
{
state
.
ui
.
isFocusMode
}
onToggle=
{
handleToggleFocusMode
}
/>
<
FocusModeOverlay
isActive=
{
state
.
ui
.
isFocusMode
}
onToggle=
{
handleToggleFocusMode
}
/>
{
/*
{
/*
...
@@ -175,10 +144,10 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
...
@@ -175,10 +144,10 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
{
/* Metadata and toolbar grouped together at bottom */
}
{
/* Metadata and toolbar grouped together at bottom */
}
<
div
className=
"w-full flex flex-col gap-2"
>
<
div
className=
"w-full flex flex-col gap-2"
>
<
EditorMetadata
/>
<
EditorMetadata
/>
<
EditorToolbar
onSave=
{
handleSave
}
onCancel=
{
onCancel
}
/>
<
EditorToolbar
onSave=
{
handleSave
}
onCancel=
{
onCancel
}
memoName=
{
memoName
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
</
MemoEditorContext
.
Provider
>
</>
);
);
};
};
...
...
web/src/components/MemoEditor/types/components.ts
0 → 100644
View file @
792d58b7
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
;
cacheKey
?:
string
;
placeholder
?:
string
;
memoName
?:
string
;
parentMemoName
?:
string
;
autoFocus
?:
boolean
;
onConfirm
?:
(
memoName
:
string
)
=>
void
;
onCancel
?:
()
=>
void
;
}
export
interface
EditorContentProps
{
placeholder
?:
string
;
autoFocus
?:
boolean
;
}
export
interface
EditorToolbarProps
{
onSave
:
()
=>
void
;
onCancel
?:
()
=>
void
;
memoName
?:
string
;
}
export
interface
EditorMetadataProps
{}
export
interface
FocusModeOverlayProps
{
isActive
:
boolean
;
onToggle
:
()
=>
void
;
}
export
interface
FocusModeExitButtonProps
{
isActive
:
boolean
;
onToggle
:
()
=>
void
;
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
;
}
export
interface
LocationDialogProps
{
open
:
boolean
;
onOpenChange
:
(
open
:
boolean
)
=>
void
;
state
:
LocationState
;
locationInitialized
:
boolean
;
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
;
onLocationChange
:
(
location
?:
Location
)
=>
void
;
onToggleFocusMode
?:
()
=>
void
;
memoName
?:
string
;
}
export
interface
TagSuggestionsProps
{
editorRef
:
React
.
RefObject
<
HTMLTextAreaElement
>
;
editorActions
:
React
.
ForwardedRef
<
EditorRefActions
>
;
}
export
interface
SlashCommandsProps
{
editorRef
:
React
.
RefObject
<
HTMLTextAreaElement
>
;
editorActions
:
React
.
ForwardedRef
<
EditorRefActions
>
;
commands
:
Command
[];
}
export
interface
EditorProps
{
className
:
string
;
initialContent
:
string
;
placeholder
:
string
;
onContentChange
:
(
content
:
string
)
=>
void
;
onPaste
:
(
event
:
React
.
ClipboardEvent
)
=>
void
;
isFocusMode
?:
boolean
;
isInIME
?:
boolean
;
onCompositionStart
?:
()
=>
void
;
onCompositionEnd
?:
()
=>
void
;
}
export
interface
VisibilitySelectorProps
{
value
:
Visibility
;
onChange
:
(
visibility
:
Visibility
)
=>
void
;
onOpenChange
?:
(
open
:
boolean
)
=>
void
;
}
web/src/components/MemoEditor/types/index.ts
View file @
792d58b7
// MemoEditor type exports
// MemoEditor type exports
export
type
{
EditorContentProps
,
EditorMetadataProps
,
EditorProps
,
EditorToolbarProps
,
FocusModeExitButtonProps
,
FocusModeOverlayProps
,
InsertMenuProps
,
LinkMemoDialogProps
,
LocationDialogProps
,
MemoEditorProps
,
SlashCommandsProps
,
TagSuggestionsProps
,
VisibilitySelectorProps
,
}
from
"./components"
;
export
{
MemoEditorContext
,
type
MemoEditorContextValue
}
from
"./context"
;
export
{
MemoEditorContext
,
type
MemoEditorContextValue
}
from
"./context"
;
export
type
{
LocationState
}
from
"./insert-menu"
;
export
type
{
LocationState
}
from
"./insert-menu"
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment