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
c4176b4e
Commit
c4176b4e
authored
Feb 07, 2026
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: videos attachment
parent
81ef53b3
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
46 additions
and
27 deletions
+46
-27
AttachmentList.tsx
...omponents/MemoView/components/metadata/AttachmentList.tsx
+46
-27
No files found.
web/src/components/MemoView/components/metadata/AttachmentList.tsx
View file @
c4176b4e
import
{
FileIcon
,
PaperclipIcon
}
from
"lucide-react"
;
import
{
FileIcon
,
PaperclipIcon
}
from
"lucide-react"
;
import
{
useState
}
from
"react"
;
import
{
use
Memo
,
use
State
}
from
"react"
;
import
type
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service_pb"
;
import
type
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service_pb"
;
import
{
getAttachmentType
,
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
{
getAttachmentType
,
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
{
formatFileSize
,
getFileTypeLabel
}
from
"@/utils/format"
;
import
{
formatFileSize
,
getFileTypeLabel
}
from
"@/utils/format"
;
...
@@ -11,13 +11,18 @@ interface AttachmentListProps {
...
@@ -11,13 +11,18 @@ interface AttachmentListProps {
attachments
:
Attachment
[];
attachments
:
Attachment
[];
}
}
// Type guards for attachment types
const
isImageAttachment
=
(
attachment
:
Attachment
):
boolean
=>
getAttachmentType
(
attachment
)
===
"image/*"
;
const
isVideoAttachment
=
(
attachment
:
Attachment
):
boolean
=>
getAttachmentType
(
attachment
)
===
"video/*"
;
const
isMediaAttachment
=
(
attachment
:
Attachment
):
boolean
=>
isImageAttachment
(
attachment
)
||
isVideoAttachment
(
attachment
);
// Separate attachments into media (images/videos) and documents
const
separateMediaAndDocs
=
(
attachments
:
Attachment
[]):
{
media
:
Attachment
[];
docs
:
Attachment
[]
}
=>
{
const
separateMediaAndDocs
=
(
attachments
:
Attachment
[]):
{
media
:
Attachment
[];
docs
:
Attachment
[]
}
=>
{
const
media
:
Attachment
[]
=
[];
const
media
:
Attachment
[]
=
[];
const
docs
:
Attachment
[]
=
[];
const
docs
:
Attachment
[]
=
[];
for
(
const
attachment
of
attachments
)
{
for
(
const
attachment
of
attachments
)
{
const
attachmentType
=
getAttachmentType
(
attachment
);
if
(
isMediaAttachment
(
attachment
))
{
if
(
attachmentType
===
"image/*"
||
attachmentType
===
"video/*"
)
{
media
.
push
(
attachment
);
media
.
push
(
attachment
);
}
else
{
}
else
{
docs
.
push
(
attachment
);
docs
.
push
(
attachment
);
...
@@ -55,27 +60,39 @@ const DocumentItem = ({ attachment }: { attachment: Attachment }) => {
...
@@ -55,27 +60,39 @@ const DocumentItem = ({ attachment }: { attachment: Attachment }) => {
);
);
};
};
const
MediaGrid
=
({
attachments
,
onImageClick
}:
{
attachments
:
Attachment
[];
onImageClick
:
(
url
:
string
)
=>
void
})
=>
(
interface
MediaItemProps
{
attachment
:
Attachment
;
onImageClick
:
(
url
:
string
)
=>
void
;
}
const
MediaItem
=
({
attachment
,
onImageClick
}:
MediaItemProps
)
=>
{
const
isImage
=
isImageAttachment
(
attachment
);
const
handleClick
=
()
=>
{
if
(
isImage
)
{
onImageClick
(
getAttachmentUrl
(
attachment
));
}
};
return
(
<
div
className=
"aspect-square rounded-lg overflow-hidden bg-muted/40 border border-border hover:border-accent/50 transition-all cursor-pointer group"
onClick=
{
handleClick
}
>
<
AttachmentCard
attachment=
{
attachment
}
className=
"rounded-none"
/>
</
div
>
);
};
interface
MediaGridProps
{
attachments
:
Attachment
[];
onImageClick
:
(
url
:
string
)
=>
void
;
}
const
MediaGrid
=
({
attachments
,
onImageClick
}:
MediaGridProps
)
=>
(
<
div
className=
"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2"
>
<
div
className=
"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2"
>
{
attachments
.
map
((
attachment
)
=>
(
{
attachments
.
map
((
attachment
)
=>
(
<
div
<
MediaItem
key=
{
attachment
.
name
}
attachment=
{
attachment
}
onImageClick=
{
onImageClick
}
/>
key=
{
attachment
.
name
}
className=
"aspect-square rounded-lg overflow-hidden bg-muted/40 border border-border hover:border-accent/50 transition-all cursor-pointer group"
onClick=
{
()
=>
onImageClick
(
getAttachmentUrl
(
attachment
))
}
>
<
div
className=
"w-full h-full relative"
>
<
AttachmentCard
attachment=
{
attachment
}
className=
"rounded-none"
/>
{
getAttachmentType
(
attachment
)
===
"video/*"
&&
(
<
div
className=
"absolute inset-0 flex items-center justify-center bg-black/30 group-hover:bg-black/40 transition-colors"
>
<
div
className=
"w-8 h-8 rounded-full bg-white/80 flex items-center justify-center"
>
<
svg
className=
"w-5 h-5 text-black fill-current ml-0.5"
viewBox=
"0 0 24 24"
>
<
path
d=
"M8 5v14l11-7z"
/>
</
svg
>
</
div
>
</
div
>
)
}
</
div
>
</
div
>
))
}
))
}
</
div
>
</
div
>
);
);
...
@@ -98,18 +115,20 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => {
...
@@ -98,18 +115,20 @@ const AttachmentList = ({ attachments }: AttachmentListProps) => {
mimeType
:
undefined
,
mimeType
:
undefined
,
});
});
const
{
media
:
mediaItems
,
docs
:
docItems
}
=
separateMediaAndDocs
(
attachments
);
const
{
media
:
mediaItems
,
docs
:
docItems
}
=
useMemo
(()
=>
separateMediaAndDocs
(
attachments
),
[
attachments
]);
// Pre-compute image URLs for preview dialog to avoid filtering on every click
const
imageAttachments
=
useMemo
(()
=>
mediaItems
.
filter
(
isImageAttachment
),
[
mediaItems
]);
const
imageUrls
=
useMemo
(()
=>
imageAttachments
.
map
(
getAttachmentUrl
),
[
imageAttachments
]);
if
(
attachments
.
length
===
0
)
{
if
(
attachments
.
length
===
0
)
{
return
null
;
return
null
;
}
}
const
handleImageClick
=
(
imgUrl
:
string
)
=>
{
const
handleImageClick
=
(
imgUrl
:
string
)
=>
{
const
imageAttachments
=
mediaItems
.
filter
((
a
)
=>
getAttachmentType
(
a
)
===
"image/*"
);
const
index
=
imageUrls
.
findIndex
((
url
)
=>
url
===
imgUrl
);
const
imgUrls
=
imageAttachments
.
map
((
a
)
=>
getAttachmentUrl
(
a
));
const
index
=
imgUrls
.
findIndex
((
url
)
=>
url
===
imgUrl
);
const
mimeType
=
imageAttachments
[
index
]?.
type
;
const
mimeType
=
imageAttachments
[
index
]?.
type
;
setPreviewImage
({
open
:
true
,
urls
:
im
g
Urls
,
index
,
mimeType
});
setPreviewImage
({
open
:
true
,
urls
:
im
age
Urls
,
index
,
mimeType
});
};
};
return
(
return
(
...
...
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