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
124708f1
Commit
124708f1
authored
Apr 08, 2026
by
boojack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: refactor attachment media layout and insert menu organization
parent
7e21b728
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
230 additions
and
92 deletions
+230
-92
InsertMenu.tsx
web/src/components/MemoEditor/Toolbar/InsertMenu.tsx
+22
-7
useFileUpload.ts
web/src/components/MemoEditor/hooks/useFileUpload.ts
+7
-2
AttachmentListView.tsx
...components/MemoMetadata/Attachment/AttachmentListView.tsx
+169
-67
AudioAttachmentItem.tsx
...omponents/MemoMetadata/Attachment/AudioAttachmentItem.tsx
+20
-15
MotionPhotoPlayer.tsx
web/src/components/MotionPhotoPlayer.tsx
+6
-1
MotionPhotoPreview.tsx
web/src/components/MotionPhotoPreview.tsx
+6
-0
No files found.
web/src/components/MemoEditor/Toolbar/InsertMenu.tsx
View file @
124708f1
...
...
@@ -2,6 +2,7 @@ import { LatLng } from "leaflet";
import
{
uniqBy
}
from
"lodash-es"
;
import
{
FileIcon
,
ImageIcon
,
LinkIcon
,
LoaderIcon
,
type
LucideIcon
,
...
...
@@ -131,14 +132,22 @@ const InsertMenu = (props: InsertMenuProps) => {
setMoreSubmenuOpen
(
false
);
},
[
onToggleFocusMode
]);
const
handleMediaUploadClick
=
useCallback
(()
=>
{
handleUploadClick
(
"image/*,video/*"
);
},
[
handleUploadClick
]);
const
handleFileUploadClick
=
useCallback
(()
=>
{
handleUploadClick
();
},
[
handleUploadClick
]);
const
menuItems
=
useMemo
(
()
=>
[
{
key
:
"upload"
,
label
:
t
(
"
editor.insert-menu.upload-file
"
),
icon
:
Fil
eIcon
,
onClick
:
handleUploadClick
,
key
:
"upload
-media
"
,
label
:
t
(
"
attachment-library.tabs.media
"
),
icon
:
Imag
eIcon
,
onClick
:
handle
Media
UploadClick
,
},
{
key
:
"record-audio"
,
...
...
@@ -146,6 +155,12 @@ const InsertMenu = (props: InsertMenuProps) => {
icon
:
MicIcon
,
onClick
:
()
=>
props
.
onAudioRecorderClick
?.(),
},
{
key
:
"upload-file"
,
label
:
t
(
"common.file"
),
icon
:
FileIcon
,
onClick
:
handleFileUploadClick
,
},
{
key
:
"link"
,
label
:
t
(
"editor.insert-menu.link-memo"
),
...
...
@@ -159,7 +174,7 @@ const InsertMenu = (props: InsertMenuProps) => {
onClick
:
handleLocationClick
,
},
]
satisfies
Array
<
{
key
:
string
;
label
:
string
;
icon
:
LucideIcon
;
onClick
:
()
=>
void
}
>
,
[
handle
LocationClick
,
handleOpenLinkDialog
,
handleUploadClick
,
props
,
t
],
[
handle
FileUploadClick
,
handleLocationClick
,
handleMediaUploadClick
,
handleOpenLinkDialog
,
props
,
t
],
);
return
(
...
...
@@ -171,14 +186,14 @@ const InsertMenu = (props: InsertMenuProps) => {
</
Button
>
</
DropdownMenuTrigger
>
<
DropdownMenuContent
align=
"start"
>
{
menuItems
.
slice
(
0
,
2
).
map
((
item
)
=>
(
{
menuItems
.
slice
(
0
,
3
).
map
((
item
)
=>
(
<
DropdownMenuItem
key=
{
item
.
key
}
onClick=
{
item
.
onClick
}
>
<
item
.
icon
className=
"w-4 h-4"
/>
{
item
.
label
}
</
DropdownMenuItem
>
))
}
<
DropdownMenuSeparator
/>
{
menuItems
.
slice
(
2
).
map
((
item
)
=>
(
{
menuItems
.
slice
(
3
).
map
((
item
)
=>
(
<
DropdownMenuItem
key=
{
item
.
key
}
onClick=
{
item
.
onClick
}
>
<
item
.
icon
className=
"w-4 h-4"
/>
{
item
.
label
}
...
...
web/src/components/MemoEditor/hooks/useFileUpload.ts
View file @
124708f1
...
...
@@ -26,8 +26,13 @@ export const useFileUpload = (onFilesSelected: (localFiles: LocalFile[]) => void
if
(
fileInputRef
.
current
)
fileInputRef
.
current
.
value
=
""
;
};
const
handleUploadClick
=
()
=>
{
fileInputRef
.
current
?.
click
();
const
handleUploadClick
=
(
accept
=
"*"
)
=>
{
if
(
!
fileInputRef
.
current
)
{
return
;
}
fileInputRef
.
current
.
accept
=
accept
;
fileInputRef
.
current
.
click
();
};
return
{
...
...
web/src/components/MemoMetadata/Attachment/AttachmentListView.tsx
View file @
124708f1
This diff is collapsed.
Click to expand it.
web/src/components/MemoMetadata/Attachment/AudioAttachmentItem.tsx
View file @
124708f1
import
{
PauseIcon
,
PlayIcon
}
from
"lucide-react"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
formatFileSize
,
getFileTypeLabel
}
from
"@/utils/format"
;
import
{
formatAudioTime
}
from
"./attachmentHelpers"
;
...
...
@@ -56,9 +57,11 @@ interface AudioAttachmentItemProps {
mimeType
:
string
;
size
?:
number
;
title
?:
string
;
compact
?:
boolean
;
className
?:
string
;
}
const
AudioAttachmentItem
=
({
filename
,
sourceUrl
,
mimeType
,
size
,
title
}:
AudioAttachmentItemProps
)
=>
{
const
AudioAttachmentItem
=
({
filename
,
sourceUrl
,
mimeType
,
size
,
title
,
compact
=
false
,
className
}:
AudioAttachmentItemProps
)
=>
{
const
audioRef
=
useRef
<
HTMLAudioElement
>
(
null
);
const
[
isPlaying
,
setIsPlaying
]
=
useState
(
false
);
const
[
currentTime
,
setCurrentTime
]
=
useState
(
0
);
...
...
@@ -119,9 +122,9 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
};
return
(
<
div
className=
"rounded-xl border border-border/40 bg-background/75 px-2 py-1.5"
>
<
div
className=
"flex items-
center justify-between gap-1.5
"
>
<
div
className=
"min-w-0 flex flex-1 items-baseline gap-1"
>
<
div
className=
{
cn
(
"rounded-xl border border-border/40 bg-background/75"
,
compact
?
"px-3 py-2.5"
:
"px-2 py-1.5"
,
className
)
}
>
<
div
className=
"flex items-
start justify-between gap-2
"
>
<
div
className=
{
cn
(
"min-w-0 flex flex-1"
,
compact
?
"flex-col gap-0.5"
:
"items-baseline gap-1"
)
}
>
<
div
className=
"truncate text-sm font-medium leading-5 text-foreground"
title=
{
filename
}
>
{
displayTitle
}
</
div
>
...
...
@@ -141,7 +144,7 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
</
button
>
</
div
>
<
div
className=
"mt-1 flex items-center gap-1"
>
<
div
className=
{
cn
(
"mt-1"
,
compact
?
"space-y-1.5"
:
"flex items-center gap-1"
)
}
>
<
AudioProgressBar
filename=
{
filename
}
currentTime=
{
currentTime
}
...
...
@@ -151,16 +154,18 @@ const AudioAttachmentItem = ({ filename, sourceUrl, mimeType, size, title }: Aud
className=
"min-w-0 flex-1"
/>
<
div
className=
"shrink-0 text-[10px] tabular-nums text-muted-foreground"
>
{
timeLabel
}
</
div
>
<
button
type=
"button"
onClick=
{
handlePlaybackRateChange
}
className=
"inline-flex h-5 shrink-0 items-center justify-center rounded-md border border-transparent px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:border-border/40 hover:text-foreground"
aria
-
label=
{
`Playback speed ${playbackRate}x for ${displayTitle}`
}
>
{
playbackRate
}
x
</
button
>
<
div
className=
{
cn
(
"flex items-center"
,
compact
?
"justify-between gap-2"
:
"shrink-0 gap-1"
)
}
>
<
div
className=
"shrink-0 text-[10px] tabular-nums text-muted-foreground"
>
{
timeLabel
}
</
div
>
<
button
type=
"button"
onClick=
{
handlePlaybackRateChange
}
className=
"inline-flex h-5 shrink-0 items-center justify-center rounded-md border border-transparent px-1 text-[10px] font-medium text-muted-foreground transition-colors hover:border-border/40 hover:text-foreground"
aria
-
label=
{
`Playback speed ${playbackRate}x for ${displayTitle}`
}
>
{
playbackRate
}
x
</
button
>
</
div
>
</
div
>
<
audio
...
...
web/src/components/MotionPhotoPlayer.tsx
View file @
124708f1
...
...
@@ -8,6 +8,8 @@ interface MotionPhotoPlayerProps {
presentationTimestampUs
?:
bigint
;
containerClassName
?:
string
;
mediaClassName
?:
string
;
posterClassName
?:
string
;
videoClassName
?:
string
;
active
?:
boolean
;
loop
?:
boolean
;
}
...
...
@@ -19,6 +21,8 @@ const MotionPhotoPlayer = ({
presentationTimestampUs
,
containerClassName
,
mediaClassName
,
posterClassName
,
videoClassName
,
active
,
loop
=
false
,
}:
MotionPhotoPlayerProps
)
=>
{
...
...
@@ -87,7 +91,7 @@ const MotionPhotoPlayer = ({
<
img
src=
{
posterUrl
}
alt=
{
alt
}
className=
{
cn
(
"block max-h-full max-w-full select-none object-cover"
,
mediaClassName
)
}
className=
{
cn
(
"block max-h-full max-w-full select-none object-cover"
,
mediaClassName
,
posterClassName
)
}
draggable=
{
false
}
loading=
"lazy"
decoding=
"async"
...
...
@@ -100,6 +104,7 @@ const MotionPhotoPlayer = ({
"pointer-events-none absolute inset-0 h-full w-full object-cover transition-opacity duration-200"
,
isPlaying
?
"opacity-100"
:
"opacity-0"
,
mediaClassName
,
videoClassName
,
)
}
muted
playsInline
...
...
web/src/components/MotionPhotoPreview.tsx
View file @
124708f1
...
...
@@ -9,6 +9,8 @@ interface MotionPhotoPreviewProps {
presentationTimestampUs
?:
bigint
;
containerClassName
?:
string
;
mediaClassName
?:
string
;
posterClassName
?:
string
;
videoClassName
?:
string
;
badgeClassName
?:
string
;
loop
?:
boolean
;
}
...
...
@@ -20,6 +22,8 @@ const MotionPhotoPreview = ({
presentationTimestampUs
,
containerClassName
,
mediaClassName
,
posterClassName
,
videoClassName
,
badgeClassName
,
loop
=
false
,
}:
MotionPhotoPreviewProps
)
=>
{
...
...
@@ -40,6 +44,8 @@ const MotionPhotoPreview = ({
loop=
{
loop
}
containerClassName=
{
cn
(
"max-w-full max-h-full"
,
containerClassName
)
}
mediaClassName=
{
mediaClassName
}
posterClassName=
{
posterClassName
}
videoClassName=
{
videoClassName
}
/>
<
div
role=
"button"
...
...
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