Skip to content

Commit 765022d

Browse files
authored
feat(view): support gallery preview for custom file types beyond images and videos (#1110)
1 parent 3fa02a9 commit 765022d

3 files changed

Lines changed: 90 additions & 54 deletions

File tree

packages/editor/src/view/hooks/useFilesGallery/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ _UseFilesGalleryOptions_
2727
| download | `(url: string, type: FilesGalleryItemType, element: Element) => string or undefined` | | | | The file download link getter (if you want to show the download action) |
2828
| copyUrl | `(url: string, type: FilesGalleryItemType, element: Element) => string or undefined` | | | | The file copy link getter (if you want to show the copy link action) |
2929
| overrideItemProps | `(url: string, type: FilesGalleryItemType, element: Element, currentProps: GalleryItemProps) => GalleryItemProps` | | | | The custom gallery item props getter (if you want to override the default gallery item props) |
30+
| resolveCustomItem | `(url: string, type: 'file', element: Element, linkObj: {name?: string or null; mimetype?: string or null}) => GalleryItemProps or undefined` | | | | Resolves base `GalleryItemProps` for elements not handled by the default image/video logic (e.g. arbitrary file links). Return `undefined` to skip the element. The returned props go through the same `download`/`copyUrl`/`overrideItemProps` pipeline with `type: 'file'`. If the returned props contain `actions`, they are merged with the auto-generated download/copy actions. Note: `FilesGalleryItemType` is now `'image' \| 'video' \| 'file'` — callers doing exhaustive `switch` on `type` in other options may need to handle the new `'file'` case. |
3031

3132

3233
_useFilesGallery returns function `openFilesGallery` with the following args_:
@@ -122,3 +123,21 @@ const {openFilesGallery} = useFilesGallery(undefined, {overrideItemProps:getGall
122123
<YfmStaticView {...props} />
123124
</div>;
124125
```
126+
127+
If you want to handle custom file types (e.g. PDF links) that are not images or videos, provide the `resolveCustomItem` option
128+
129+
```tsx
130+
import {YfmStaticView, useFilesGallery} from '@gravity-ui/markdown-editor/view';
131+
import {getGalleryItemImage} from '@gravity-ui/components';
132+
133+
function resolveCustomItem(url: string, type: 'file', element: Element, {name, mimetype}: {name?: string | null; mimetype?: string | null}) {
134+
if (mimetype !== 'application/pdf') return undefined;
135+
return getGalleryItemImage({src: '/icons/pdf.svg', name: name ?? url});
136+
}
137+
138+
const {openFilesGallery} = useFilesGallery(undefined, {resolveCustomItem});
139+
140+
<div onClick={openFilesGallery}>
141+
<YfmStaticView {...props} />
142+
</div>;
143+
```

packages/editor/src/view/hooks/useFilesGallery/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export type GalleryItemPropsWithUrl = GalleryItemProps & {
55
url?: string;
66
};
77

8-
export type FilesGalleryItemType = 'image' | 'video';
8+
export type FilesGalleryItemType = 'image' | 'video' | 'file';
99

1010
export type UseFilesGalleryOptions = {
1111
download?: (url: string, type: FilesGalleryItemType, element: Element) => string | undefined;
@@ -16,4 +16,10 @@ export type UseFilesGalleryOptions = {
1616
element: Element,
1717
currentProps: GalleryItemProps,
1818
) => GalleryItemProps;
19+
resolveCustomItem?: (
20+
url: string,
21+
type: 'file',
22+
element: Element,
23+
linkObj: {name?: string | null; mimetype?: string | null},
24+
) => GalleryItemProps | undefined;
1925
};

packages/editor/src/view/hooks/useFilesGallery/useFilesGallery.tsx

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function useFilesGallery(
2222
download: getItemDownloladUrl,
2323
overrideItemProps,
2424
copyUrl: getItemCopyUrl,
25+
resolveCustomItem,
2526
}: UseFilesGalleryOptions = {},
2627
) {
2728
const {openGallery} = useGallery();
@@ -42,6 +43,48 @@ export function useFilesGallery(
4243
return false;
4344
}
4445

46+
const buildItem = (
47+
link: string,
48+
type: FilesGalleryItemType,
49+
element: Element,
50+
baseProps: GalleryItemPropsWithUrl,
51+
): GalleryItemPropsWithUrl => {
52+
const galleryItemActions: GalleryItemAction[] = [...(baseProps.actions ?? [])];
53+
54+
const itemCopyUrl = getItemCopyUrl?.(link, type, element);
55+
if (itemCopyUrl) {
56+
const handleLinkCopied = () => {
57+
toaster.add({
58+
theme: 'success',
59+
name: 'g-md-editor-gallery-copy-link',
60+
title: i18n('link_copied'),
61+
});
62+
};
63+
galleryItemActions.push(
64+
getGalleryItemCopyLinkAction({
65+
copyUrl: itemCopyUrl,
66+
onCopy: handleLinkCopied,
67+
}),
68+
);
69+
}
70+
71+
const downloadUrl = getItemDownloladUrl?.(link, type, element);
72+
if (downloadUrl) {
73+
galleryItemActions.push(getGalleryItemDownloadAction({downloadUrl}));
74+
}
75+
76+
const galleryItemProps: GalleryItemPropsWithUrl = {
77+
...baseProps,
78+
url: link,
79+
actions: galleryItemActions,
80+
};
81+
82+
return {
83+
...galleryItemProps,
84+
...overrideItemProps?.(link, type, element, galleryItemProps),
85+
};
86+
};
87+
4588
const targetFile = buildLinkObject(event.target);
4689

4790
if (!targetFile || !targetFile.link) return false;
@@ -60,63 +103,30 @@ export function useFilesGallery(
60103
if (linkObj.type === 'image' || supportedExtensions.includes(extension)) {
61104
const link = linkObj.link;
62105
const name = linkObj.name || '';
63-
64-
const filesGalleryItemType: FilesGalleryItemType =
65-
supportedVideoExtensions.includes(extension) ? 'video' : 'image';
66-
const galleryItemActions: GalleryItemAction[] = [];
67-
68-
const itemCopyUrl = getItemCopyUrl?.(
69-
link,
70-
filesGalleryItemType,
71-
element,
72-
);
73-
74-
if (itemCopyUrl) {
75-
const handleLinkCopied = () => {
76-
toaster.add({
77-
theme: 'success',
78-
name: 'g-md-editor-gallery-copy-link',
79-
title: i18n('link_copied'),
80-
});
81-
};
82-
83-
galleryItemActions.push(
84-
getGalleryItemCopyLinkAction({
85-
copyUrl: itemCopyUrl,
86-
onCopy: handleLinkCopied,
87-
}),
88-
);
89-
}
90-
91-
const downloadUrl = getItemDownloladUrl?.(
92-
link,
93-
filesGalleryItemType,
94-
element,
95-
);
96-
97-
if (downloadUrl) {
98-
galleryItemActions.push(
99-
getGalleryItemDownloadAction({downloadUrl}),
100-
);
101-
}
102-
103-
const galleryItemProps = {
104-
...(filesGalleryItemType === 'video'
105-
? getGalleryItemVideo({src: link, name: name})
106-
: getGalleryItemImage({src: link, name: name})),
106+
const type: FilesGalleryItemType = supportedVideoExtensions.includes(
107+
extension,
108+
)
109+
? 'video'
110+
: 'image';
111+
const baseProps: GalleryItemPropsWithUrl = {
112+
...(type === 'video'
113+
? getGalleryItemVideo({src: link, name})
114+
: getGalleryItemImage({src: link, name})),
107115
url: link,
108-
actions: galleryItemActions,
116+
actions: undefined,
109117
};
110118

111-
result.push({
112-
...galleryItemProps,
113-
...overrideItemProps?.(
114-
link,
115-
filesGalleryItemType,
116-
element,
117-
galleryItemProps,
118-
),
119+
result.push(buildItem(link, type, element, baseProps));
120+
} else if (resolveCustomItem) {
121+
const link = linkObj.link;
122+
const baseProps = resolveCustomItem(link, 'file', element, {
123+
name: linkObj.name,
124+
mimetype: linkObj.mimetype,
119125
});
126+
127+
if (baseProps) {
128+
result.push(buildItem(link, 'file', element, baseProps));
129+
}
120130
}
121131
}
122132

@@ -140,6 +150,7 @@ export function useFilesGallery(
140150
getItemCopyUrl,
141151
getItemDownloladUrl,
142152
overrideItemProps,
153+
resolveCustomItem,
143154
toaster,
144155
openGallery,
145156
],

0 commit comments

Comments
 (0)