From a0d08809d0f1735ae4ed211ad4c97e0f0aac4bfe Mon Sep 17 00:00:00 2001 From: Felix Feng Date: Fri, 15 Nov 2024 21:39:55 +0800 Subject: [PATCH] fix --- .../default/plate-ui/media-dropdown-menu.tsx | 102 ++++++++++-------- .../default/plate-ui/media-embed-popover.tsx | 69 ++++++------ .../lib/media-embed/BaseMediaEmbedPlugin.ts | 14 +-- .../src/react/media/MediaEmbedPlugin.tsx | 41 ------- .../media/src/react/media/Popover/index.ts | 7 -- .../react/media/Popover/useMediaEmbedEnter.ts | 22 ---- .../media/Popover/useMediaEmbedEscape.ts | 26 ----- .../media/Popover/useMediaEmbedPopover.ts | 47 -------- packages/media/src/react/media/index.ts | 2 - packages/media/src/react/plugins.ts | 3 + .../src/react/getLastBlockDOMNode.ts | 8 ++ packages/plate-utils/src/react/index.ts | 1 + .../plate-utils/src/react/useLastBlock.ts | 15 +++ .../src/react/useLastBlockDOMNode.ts | 26 +++++ 14 files changed, 148 insertions(+), 235 deletions(-) delete mode 100644 packages/media/src/react/media/MediaEmbedPlugin.tsx delete mode 100644 packages/media/src/react/media/Popover/index.ts delete mode 100644 packages/media/src/react/media/Popover/useMediaEmbedEnter.ts delete mode 100644 packages/media/src/react/media/Popover/useMediaEmbedEscape.ts delete mode 100644 packages/media/src/react/media/Popover/useMediaEmbedPopover.ts create mode 100644 packages/plate-utils/src/react/getLastBlockDOMNode.ts create mode 100644 packages/plate-utils/src/react/useLastBlock.ts create mode 100644 packages/plate-utils/src/react/useLastBlockDOMNode.ts diff --git a/apps/www/src/registry/default/plate-ui/media-dropdown-menu.tsx b/apps/www/src/registry/default/plate-ui/media-dropdown-menu.tsx index c307c21459..c69da258ce 100644 --- a/apps/www/src/registry/default/plate-ui/media-dropdown-menu.tsx +++ b/apps/www/src/registry/default/plate-ui/media-dropdown-menu.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; @@ -31,6 +31,7 @@ import { DropdownMenuTrigger, useOpenState, } from './dropdown-menu'; +import { MediaEmbedPopover } from './media-embed-popover'; import { ToolbarSplitButton, ToolbarSplitButtonPrimary, @@ -72,9 +73,10 @@ export function MediaDropdownMenu({ nodeType, ...props }: DropdownMenuProps & { nodeType: string }) { - const currentConfig = MEDIA_CONFIG[nodeType]; + const { editor } = useEditorPlugin(MediaEmbedPlugin); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const { editor, setOptions } = useEditorPlugin(MediaEmbedPlugin); + const currentConfig = MEDIA_CONFIG[nodeType]; const { openFilePicker } = useFilePicker({ accept: currentConfig.accept, multiple: true, @@ -86,50 +88,58 @@ export function MediaDropdownMenu({ const openState = useOpenState(); return ( - - - openFilePicker()}> - {currentConfig.icon} - + <> + + + openFilePicker()}> + {currentConfig.icon} + + + + + + - - - - + + + openFilePicker()} + hideIcon + > +
+ {currentConfig.icon} + Upload from computer +
+
+ { + focusEditor(editor); + setIsPopoverOpen(true); + }} + hideIcon + > +
+ + Insert via URL +
+
+
+
+
- - - openFilePicker()} - hideIcon - > -
- {currentConfig.icon} - Upload from computer -
-
- { - focusEditor(editor); - setOptions({ isOpen: true, mediaType: nodeType }); - }} - hideIcon - > -
- - Insert via URL -
-
-
-
- + + ); } diff --git a/apps/www/src/registry/default/plate-ui/media-embed-popover.tsx b/apps/www/src/registry/default/plate-ui/media-embed-popover.tsx index 6c69f055c7..6d23701215 100644 --- a/apps/www/src/registry/default/plate-ui/media-embed-popover.tsx +++ b/apps/www/src/registry/default/plate-ui/media-embed-popover.tsx @@ -1,18 +1,18 @@ 'use client'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; -import { useEditorPlugin, useEditorSelector } from '@udecode/plate-core/react'; +import { focusEditor } from '@udecode/plate-common/react'; +import { useEditorPlugin } from '@udecode/plate-core/react'; +import { insertImage } from '@udecode/plate-media'; import { AudioPlugin, FilePlugin, ImagePlugin, MediaEmbedPlugin, VideoPlugin, - useMediaEmbedPopover, } from '@udecode/plate-media/react'; -import { toDOMNode } from '@udecode/slate-react'; -import { getAncestorNode } from '@udecode/slate-utils'; +import { useLastBlockDOMNode } from '@udecode/plate-utils/react'; import { AudioLinesIcon, FileUpIcon, FilmIcon, ImageIcon } from 'lucide-react'; import { Button } from './button'; @@ -45,24 +45,25 @@ const MEDIA_CONFIG: Record< }, }; -export function MediaEmbedPopover() { - const { setOption, useOption } = useEditorPlugin(MediaEmbedPlugin); - - const mediaType = useOption('mediaType'); - const isOpen = useOption('isOpen'); - - const anchorElement = useEditorSelector( - (editor) => { - if (!isOpen) return null; - - const enter = getAncestorNode(editor); - - if (!enter) return null; - - return toDOMNode(editor, enter[0]); - }, - [isOpen] - ); +export function MediaEmbedPopover({ + isOpen, + mediaType, + onOpenChange, +}: { + isOpen: boolean; + mediaType: string; + onOpenChange: (open: boolean) => void; +}) { + const { editor } = useEditorPlugin(MediaEmbedPlugin); + const [url, setUrl] = useState(''); + + const anchorElement = useLastBlockDOMNode(editor, { enabled: isOpen }); + + const embedMedia = useCallback(() => { + insertImage(editor, url); + focusEditor(editor); + onOpenChange(false); + }, [editor, url, onOpenChange]); const mediaConfig = useMemo(() => { if (!mediaType) return null; @@ -70,16 +71,10 @@ export function MediaEmbedPopover() { return MEDIA_CONFIG[mediaType]; }, [mediaType]); - const { acceptProps, cancelProps, inputProps } = useMediaEmbedPopover(); - if (!anchorElement) return null; return ( - setOption('isOpen', open)} - modal={false} - > + @@ -90,17 +85,25 @@ export function MediaEmbedPopover() { setUrl(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') embedMedia(); + }} placeholder={mediaConfig?.placeholder} h="sm" />
- -
diff --git a/packages/media/src/lib/media-embed/BaseMediaEmbedPlugin.ts b/packages/media/src/lib/media-embed/BaseMediaEmbedPlugin.ts index 066e95db0c..9cfe21f11d 100644 --- a/packages/media/src/lib/media-embed/BaseMediaEmbedPlugin.ts +++ b/packages/media/src/lib/media-embed/BaseMediaEmbedPlugin.ts @@ -1,8 +1,4 @@ -import { - type PluginConfig, - createTSlatePlugin, - isUrl, -} from '@udecode/plate-common'; +import { type PluginConfig, createTSlatePlugin } from '@udecode/plate-common'; import type { MediaPluginOptions, TMediaElement } from '../media/index'; @@ -10,20 +6,16 @@ import { parseIframeUrl } from './parseIframeUrl'; export interface TMediaEmbedElement extends TMediaElement {} -export type BaseMediaEmbedConfig = PluginConfig< - 'media_embed', - MediaPluginOptions ->; +export type MediaEmbedConfig = PluginConfig<'media_embed', MediaPluginOptions>; /** * Enables support for embeddable media such as YouTube or Vimeo videos, * Instagram posts and tweets or Google Maps. */ -export const BaseMediaEmbedPlugin = createTSlatePlugin({ +export const BaseMediaEmbedPlugin = createTSlatePlugin({ key: 'media_embed', node: { isElement: true, isVoid: true }, options: { - isUrl: isUrl, transformUrl: parseIframeUrl, }, }).extend(({ type }) => ({ diff --git a/packages/media/src/react/media/MediaEmbedPlugin.tsx b/packages/media/src/react/media/MediaEmbedPlugin.tsx deleted file mode 100644 index 474ecd72c4..0000000000 --- a/packages/media/src/react/media/MediaEmbedPlugin.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { ExtendConfig } from '@udecode/plate-common'; - -import { focusEditor, toTPlatePlugin } from '@udecode/plate-common/react'; - -import { - type BaseMediaEmbedConfig, - BaseMediaEmbedPlugin, - insertImage, -} from '../../lib'; - -type MediaEmbedConfig = ExtendConfig< - BaseMediaEmbedConfig, - { - isOpen?: boolean; - mediaType?: string | null; - url?: string; - } ->; - -export const MediaEmbedPlugin = toTPlatePlugin( - BaseMediaEmbedPlugin, - { - options: { - isOpen: false, - mediaType: null, - url: '', - }, - } -).extendTransforms(({ editor, getOptions, setOptions }) => ({ - embed: (url: string) => { - setOptions({ isOpen: false, url }); - - const isUrl = getOptions().isUrl; - - if (!isUrl?.(url)) return; - - insertImage(editor, url); - - focusEditor(editor); - }, -})); diff --git a/packages/media/src/react/media/Popover/index.ts b/packages/media/src/react/media/Popover/index.ts deleted file mode 100644 index 2d88454c6b..0000000000 --- a/packages/media/src/react/media/Popover/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @file Automatically generated by barrelsby. - */ - -export * from './useMediaEmbedEnter'; -export * from './useMediaEmbedEscape'; -export * from './useMediaEmbedPopover'; diff --git a/packages/media/src/react/media/Popover/useMediaEmbedEnter.ts b/packages/media/src/react/media/Popover/useMediaEmbedEnter.ts deleted file mode 100644 index 4d74077f6c..0000000000 --- a/packages/media/src/react/media/Popover/useMediaEmbedEnter.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useEditorPlugin, useHotkeys } from '@udecode/plate-common/react'; - -import { MediaEmbedPlugin } from '../../../react'; - -export const useMediaEmbedEnter = () => { - const { tf, useOption } = useEditorPlugin(MediaEmbedPlugin); - - const isOpen = useOption('isOpen'); - const url = useOption('url'); - - useHotkeys( - 'enter', - () => { - tf.media_embed.embed(url!); - }, - { - enableOnFormTags: ['INPUT'], - enabled: isOpen, - }, - [url] - ); -}; diff --git a/packages/media/src/react/media/Popover/useMediaEmbedEscape.ts b/packages/media/src/react/media/Popover/useMediaEmbedEscape.ts deleted file mode 100644 index 603cea3558..0000000000 --- a/packages/media/src/react/media/Popover/useMediaEmbedEscape.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - focusEditor, - useEditorPlugin, - useHotkeys, -} from '@udecode/plate-common/react'; - -import { MediaEmbedPlugin } from '../MediaEmbedPlugin'; - -export const useMediaEmbedEscape = () => { - const { editor, setOptions, useOption } = useEditorPlugin(MediaEmbedPlugin); - - const isOpen = useOption('isOpen'); - - useHotkeys( - 'escape', - () => { - setOptions({ isOpen: false, url: '' }); - focusEditor(editor); - }, - { - enableOnFormTags: ['INPUT'], - enabled: isOpen, - }, - [] - ); -}; diff --git a/packages/media/src/react/media/Popover/useMediaEmbedPopover.ts b/packages/media/src/react/media/Popover/useMediaEmbedPopover.ts deleted file mode 100644 index a12b3f7754..0000000000 --- a/packages/media/src/react/media/Popover/useMediaEmbedPopover.ts +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useEffect } from 'react'; - -import { focusEditor, useEditorPlugin } from '@udecode/plate-common/react'; - -import { MediaEmbedPlugin } from '../MediaEmbedPlugin'; -import { useMediaEmbedEnter } from './useMediaEmbedEnter'; -import { useMediaEmbedEscape } from './useMediaEmbedEscape'; - -export const useMediaEmbedPopover = () => { - const { editor, setOption, tf, useOption } = - useEditorPlugin(MediaEmbedPlugin); - const isOpen = useOption('isOpen'); - const url = useOption('url'); - - const handleCancel = () => { - setOption('isOpen', false); - focusEditor(editor); - }; - - const inputRef = React.useRef(null); - - useEffect(() => { - if (!isOpen) return; - - setTimeout(() => { - inputRef.current?.focus({ preventScroll: true }); - }, 0); - }, [isOpen]); - - useMediaEmbedEscape(); - useMediaEmbedEnter(); - - return { - acceptProps: { - onClick: () => tf.media_embed.embed(url!), - }, - cancelProps: { - onClick: handleCancel, - }, - inputProps: { - ref: inputRef, - value: useOption('url'), - onChange: (e: React.ChangeEvent) => - setOption('url', e.target.value), - }, - }; -}; diff --git a/packages/media/src/react/media/index.ts b/packages/media/src/react/media/index.ts index d3abb7a11a..524227b5ce 100644 --- a/packages/media/src/react/media/index.ts +++ b/packages/media/src/react/media/index.ts @@ -2,10 +2,8 @@ * @file Automatically generated by barrelsby. */ -export * from './MediaEmbedPlugin'; export * from './mediaStore'; export * from './useMediaController'; export * from './useMediaState'; export * from './useMediaToolbarButton'; export * from './FloatingMedia/index'; -export * from './Popover/index'; diff --git a/packages/media/src/react/plugins.ts b/packages/media/src/react/plugins.ts index 253f5ff4a2..c154d9fbfb 100644 --- a/packages/media/src/react/plugins.ts +++ b/packages/media/src/react/plugins.ts @@ -4,11 +4,14 @@ import { BaseAudioPlugin, BaseFilePlugin, BaseImagePlugin, + BaseMediaEmbedPlugin, BaseVideoPlugin, } from '../lib'; export const ImagePlugin = toPlatePlugin(BaseImagePlugin); +export const MediaEmbedPlugin = toPlatePlugin(BaseMediaEmbedPlugin); + export const AudioPlugin = toPlatePlugin(BaseAudioPlugin); export const FilePlugin = toPlatePlugin(BaseFilePlugin); diff --git a/packages/plate-utils/src/react/getLastBlockDOMNode.ts b/packages/plate-utils/src/react/getLastBlockDOMNode.ts new file mode 100644 index 0000000000..95e92f4075 --- /dev/null +++ b/packages/plate-utils/src/react/getLastBlockDOMNode.ts @@ -0,0 +1,8 @@ +import type { PlateEditor } from '@udecode/plate-core/react'; + +import { toDOMNode } from '@udecode/slate-react'; +import { getBlocks } from '@udecode/slate-utils'; + +export const getLastBlockDOMNode = (editor: PlateEditor) => { + return toDOMNode(editor, getBlocks(editor).at(-1)![0]); +}; diff --git a/packages/plate-utils/src/react/index.ts b/packages/plate-utils/src/react/index.ts index e470d2d827..4b3ed12851 100644 --- a/packages/plate-utils/src/react/index.ts +++ b/packages/plate-utils/src/react/index.ts @@ -9,6 +9,7 @@ export * from './createNodesHOC'; export * from './selectEditor'; export * from './selectSiblingNodePoint'; export * from './useFormInputProps'; +export * from './useLastBlockDOMNode'; export * from './useMarkToolbarButton'; export * from './usePlaceholder'; export * from './useRemoveNodeButton'; diff --git a/packages/plate-utils/src/react/useLastBlock.ts b/packages/plate-utils/src/react/useLastBlock.ts new file mode 100644 index 0000000000..708d4ba3df --- /dev/null +++ b/packages/plate-utils/src/react/useLastBlock.ts @@ -0,0 +1,15 @@ +import { useEditorSelector } from '@udecode/plate-core/react'; +import { getBlocks } from '@udecode/slate-utils'; + +export const useLastBlock = ({ + deps, + enabled, +}: { + enabled: boolean; + deps?: React.DependencyList; +}) => { + return useEditorSelector( + (editor) => (enabled ? getBlocks(editor).at(-1)![0] : null), + [enabled, ...(deps || [])] + ); +}; diff --git a/packages/plate-utils/src/react/useLastBlockDOMNode.ts b/packages/plate-utils/src/react/useLastBlockDOMNode.ts new file mode 100644 index 0000000000..f12977cb7e --- /dev/null +++ b/packages/plate-utils/src/react/useLastBlockDOMNode.ts @@ -0,0 +1,26 @@ +import { useMemo } from 'react'; + +import type { PlateEditor } from '@udecode/plate-core/react'; + +import { toDOMNode } from '@udecode/slate-react'; + +import { useLastBlock } from './useLastBlock'; + +interface UseLastBlockDOMNodeOptions { + enabled: boolean; + deps?: React.DependencyList; +} + +export const useLastBlockDOMNode = ( + editor: PlateEditor, + { deps, enabled }: UseLastBlockDOMNodeOptions +) => { + const lastBlock = useLastBlock({ deps, enabled }); + + const anchorElement = useMemo( + () => (lastBlock ? toDOMNode(editor, lastBlock) : null)!, + [editor, lastBlock] + ); + + return anchorElement; +};