diff --git a/packages/editor/src/view/hooks/useFilesGallery/README.md b/packages/editor/src/view/hooks/useFilesGallery/README.md index 6b421b0b8..93ce67518 100644 --- a/packages/editor/src/view/hooks/useFilesGallery/README.md +++ b/packages/editor/src/view/hooks/useFilesGallery/README.md @@ -27,6 +27,7 @@ _UseFilesGalleryOptions_ | download | `(url: string, type: FilesGalleryItemType, element: Element) => string or undefined` | | | | The file download link getter (if you want to show the download action) | | copyUrl | `(url: string, type: FilesGalleryItemType, element: Element) => string or undefined` | | | | The file copy link getter (if you want to show the copy link action) | | 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) | +| 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. | _useFilesGallery returns function `openFilesGallery` with the following args_: @@ -122,3 +123,21 @@ const {openFilesGallery} = useFilesGallery(undefined, {overrideItemProps:getGall ; ``` + +If you want to handle custom file types (e.g. PDF links) that are not images or videos, provide the `resolveCustomItem` option + +```tsx +import {YfmStaticView, useFilesGallery} from '@gravity-ui/markdown-editor/view'; +import {getGalleryItemImage} from '@gravity-ui/components'; + +function resolveCustomItem(url: string, type: 'file', element: Element, {name, mimetype}: {name?: string | null; mimetype?: string | null}) { + if (mimetype !== 'application/pdf') return undefined; + return getGalleryItemImage({src: '/icons/pdf.svg', name: name ?? url}); +} + +const {openFilesGallery} = useFilesGallery(undefined, {resolveCustomItem}); + +
+ +
; +``` diff --git a/packages/editor/src/view/hooks/useFilesGallery/types.ts b/packages/editor/src/view/hooks/useFilesGallery/types.ts index d20448adf..cc00d4a71 100644 --- a/packages/editor/src/view/hooks/useFilesGallery/types.ts +++ b/packages/editor/src/view/hooks/useFilesGallery/types.ts @@ -5,7 +5,7 @@ export type GalleryItemPropsWithUrl = GalleryItemProps & { url?: string; }; -export type FilesGalleryItemType = 'image' | 'video'; +export type FilesGalleryItemType = 'image' | 'video' | 'file'; export type UseFilesGalleryOptions = { download?: (url: string, type: FilesGalleryItemType, element: Element) => string | undefined; @@ -16,4 +16,10 @@ export type UseFilesGalleryOptions = { element: Element, currentProps: GalleryItemProps, ) => GalleryItemProps; + resolveCustomItem?: ( + url: string, + type: 'file', + element: Element, + linkObj: {name?: string | null; mimetype?: string | null}, + ) => GalleryItemProps | undefined; }; diff --git a/packages/editor/src/view/hooks/useFilesGallery/useFilesGallery.tsx b/packages/editor/src/view/hooks/useFilesGallery/useFilesGallery.tsx index 2fa93562b..6382e4d9e 100644 --- a/packages/editor/src/view/hooks/useFilesGallery/useFilesGallery.tsx +++ b/packages/editor/src/view/hooks/useFilesGallery/useFilesGallery.tsx @@ -22,6 +22,7 @@ export function useFilesGallery( download: getItemDownloladUrl, overrideItemProps, copyUrl: getItemCopyUrl, + resolveCustomItem, }: UseFilesGalleryOptions = {}, ) { const {openGallery} = useGallery(); @@ -42,6 +43,48 @@ export function useFilesGallery( return false; } + const buildItem = ( + link: string, + type: FilesGalleryItemType, + element: Element, + baseProps: GalleryItemPropsWithUrl, + ): GalleryItemPropsWithUrl => { + const galleryItemActions: GalleryItemAction[] = [...(baseProps.actions ?? [])]; + + const itemCopyUrl = getItemCopyUrl?.(link, type, element); + if (itemCopyUrl) { + const handleLinkCopied = () => { + toaster.add({ + theme: 'success', + name: 'g-md-editor-gallery-copy-link', + title: i18n('link_copied'), + }); + }; + galleryItemActions.push( + getGalleryItemCopyLinkAction({ + copyUrl: itemCopyUrl, + onCopy: handleLinkCopied, + }), + ); + } + + const downloadUrl = getItemDownloladUrl?.(link, type, element); + if (downloadUrl) { + galleryItemActions.push(getGalleryItemDownloadAction({downloadUrl})); + } + + const galleryItemProps: GalleryItemPropsWithUrl = { + ...baseProps, + url: link, + actions: galleryItemActions, + }; + + return { + ...galleryItemProps, + ...overrideItemProps?.(link, type, element, galleryItemProps), + }; + }; + const targetFile = buildLinkObject(event.target); if (!targetFile || !targetFile.link) return false; @@ -60,63 +103,30 @@ export function useFilesGallery( if (linkObj.type === 'image' || supportedExtensions.includes(extension)) { const link = linkObj.link; const name = linkObj.name || ''; - - const filesGalleryItemType: FilesGalleryItemType = - supportedVideoExtensions.includes(extension) ? 'video' : 'image'; - const galleryItemActions: GalleryItemAction[] = []; - - const itemCopyUrl = getItemCopyUrl?.( - link, - filesGalleryItemType, - element, - ); - - if (itemCopyUrl) { - const handleLinkCopied = () => { - toaster.add({ - theme: 'success', - name: 'g-md-editor-gallery-copy-link', - title: i18n('link_copied'), - }); - }; - - galleryItemActions.push( - getGalleryItemCopyLinkAction({ - copyUrl: itemCopyUrl, - onCopy: handleLinkCopied, - }), - ); - } - - const downloadUrl = getItemDownloladUrl?.( - link, - filesGalleryItemType, - element, - ); - - if (downloadUrl) { - galleryItemActions.push( - getGalleryItemDownloadAction({downloadUrl}), - ); - } - - const galleryItemProps = { - ...(filesGalleryItemType === 'video' - ? getGalleryItemVideo({src: link, name: name}) - : getGalleryItemImage({src: link, name: name})), + const type: FilesGalleryItemType = supportedVideoExtensions.includes( + extension, + ) + ? 'video' + : 'image'; + const baseProps: GalleryItemPropsWithUrl = { + ...(type === 'video' + ? getGalleryItemVideo({src: link, name}) + : getGalleryItemImage({src: link, name})), url: link, - actions: galleryItemActions, + actions: undefined, }; - result.push({ - ...galleryItemProps, - ...overrideItemProps?.( - link, - filesGalleryItemType, - element, - galleryItemProps, - ), + result.push(buildItem(link, type, element, baseProps)); + } else if (resolveCustomItem) { + const link = linkObj.link; + const baseProps = resolveCustomItem(link, 'file', element, { + name: linkObj.name, + mimetype: linkObj.mimetype, }); + + if (baseProps) { + result.push(buildItem(link, 'file', element, baseProps)); + } } } @@ -140,6 +150,7 @@ export function useFilesGallery( getItemCopyUrl, getItemDownloladUrl, overrideItemProps, + resolveCustomItem, toaster, openGallery, ],