'use client' import * as React from 'react' import type { AdminMedia } from '@admin/types' import type { NodeViewProps } from '@tiptap/react' import { MediaGalleryDialog } from '@admin/components/shared/media/media-gallery-dialog' import { Button } from '@admin/components/ui/button' import { focusNextNode } from '@admin/utils/editor/focus-next-node' import { getMediaLabel } from '@admin/utils/editor/get-media-label' import { getNumberAttr } from '@admin/utils/editor/get-number-attr' import { getStringAttr } from '@admin/utils/editor/get-string-attr' import { isValidPosition } from '@admin/utils/editor/is-valid-position' import { cn } from '@admin/utils/shared/cn' import { NodeViewWrapper } from '@tiptap/react' import NextImage from 'next/image' export const RemovableImageNode: React.FC = (props) => { const { editor, node } = props const src = getStringAttr(node.attrs.src) const alt = getStringAttr(node.attrs.alt) const title = getStringAttr(node.attrs.title) const width = getNumberAttr(node.attrs.width) const height = getNumberAttr(node.attrs.height) const aspectRatio = width && height ? `${width} / ${height}` : '16 / 9' const [isDialogOpen, setIsDialogOpen] = React.useState(false) const [isControlsVisible, setIsControlsVisible] = React.useState(false) const handleChangeClick = React.useCallback((event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() setIsDialogOpen(true) }, []) const handleMediaSelected = React.useCallback( (selected: AdminMedia | AdminMedia[]) => { const item = Array.isArray(selected) ? selected[0] : selected const pos = props.getPos() if (!item?.url || !isValidPosition(pos)) return const label = item.alt || getMediaLabel(item.url) editor.view.dispatch( editor.state.tr.setNodeMarkup(pos, undefined, { ...node.attrs, src: item.url, alt: label, title: item.filename || label, width: item.width ?? null, height: item.height ?? null }) ) editor.commands.focus() }, [editor, node.attrs, props] ) const handleImageLoad = React.useCallback( (event: React.SyntheticEvent) => { if (width && height) return const image = event.currentTarget if (!image.naturalWidth || !image.naturalHeight) return const pos = props.getPos() if (!isValidPosition(pos)) return const tr = editor.state.tr .setNodeMarkup(pos, undefined, { ...node.attrs, width: image.naturalWidth, height: image.naturalHeight }) .setMeta('addToHistory', false) editor.view.dispatch(tr) }, [editor, height, node.attrs, props, width] ) const handleRemove = React.useCallback( (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() const pos = props.getPos() if (!isValidPosition(pos)) return editor .chain() .focus() .deleteRange({ from: pos, to: pos + node.nodeSize }) .run() focusNextNode(editor) }, [editor, node.nodeSize, props] ) return ( setIsControlsVisible(true)} onMouseMove={() => setIsControlsVisible(true)} onMouseOver={() => setIsControlsVisible(true)} onMouseLeave={() => setIsControlsVisible(false)} onFocusCapture={() => setIsControlsVisible(true)} onBlurCapture={(event: React.FocusEvent) => { if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { setIsControlsVisible(false) } }} tabIndex={0} style={{ width: width ? `${width}px` : '100%' }} >
{src && ( )} {editor.isEditable && (
)}
{editor.isEditable && ( )}
) }