import { NodeViewProps } from "@tiptap/core"; import { CSSProperties, useRef, useState } from "react"; import { useEvent } from "../hooks/useEvent"; import { NodeViewWrapper } from "@tiptap/react"; import { Popover, PopoverContent, PopoverTrigger, } from "@unsend/ui/src/popover"; import { ExpandIcon, ScanIcon, LinkIcon, ImageIcon, TypeIcon, } from "lucide-react"; import { Input } from "@unsend/ui/src/input"; import { BorderWidth } from "../components/ui/icons/BorderWidth"; import { ColorPickerPopup } from "../components/ui/ColorPicker"; import { AllowedAlignments } from "../types"; import { Button } from "@unsend/ui/src/button"; import { AlignmentIcon } from "../components/ui/icons/AlignmentIcon"; import { Tooltip, TooltipProvider, TooltipContent, TooltipTrigger, } from "@unsend/ui/src/tooltip"; import { Separator } from "@unsend/ui/src/separator"; import Spinner from "@unsend/ui/src/spinner"; import { LinkEditorPanel } from "../components/panels/LinkEditorPanel"; import { TextEditorPanel } from "../components/panels/TextEditorPanel"; const alignments: Array = ["left", "center", "right"]; const MIN_WIDTH = 60; export function ResizableImageTemplate(props: NodeViewProps) { const { node, updateAttributes, selected } = props; const imgRef = useRef(null); const [resizingStyle, setResizingStyle] = useState< Pick | undefined >(); let { alignment = "center", width, height, borderRadius, borderWidth, borderColor, src, } = node.attrs || {}; const [widthState, setWidthState] = useState(width.toString()); const [openLink, setOpenLink] = useState(false); const [openImgSrc, setOpenImgSrc] = useState(false); const [openAltText, setOpenAltText] = useState(false); const handleMouseDown = useEvent( (event: React.MouseEvent) => { const imageParent = document.querySelector( ".ProseMirror-selectednode" ) as HTMLDivElement; if (!imgRef.current || !imageParent || !selected) { return; } const imageParentWidth = imageParent.offsetWidth; event.preventDefault(); const direction = event.currentTarget.dataset.direction || "--"; const initialXPosition = event.clientX; const currentWidth = imgRef.current.width; const currentHeight = imgRef.current.height; let newWidth = currentWidth; let newHeight = currentHeight; const transform = direction === "left" ? -1 : 1; const removeListeners = () => { window.removeEventListener("mousemove", mouseMoveHandler); window.removeEventListener("mouseup", removeListeners); updateAttributes({ width: newWidth, height: newHeight }); setResizingStyle(undefined); }; const mouseMoveHandler = (event: MouseEvent) => { newWidth = Math.max( currentWidth + transform * (event.clientX - initialXPosition), MIN_WIDTH ); if (newWidth > imageParentWidth) { newWidth = imageParentWidth; } newHeight = (newWidth / currentWidth) * currentHeight; setResizingStyle({ width: newWidth, height: newHeight }); setWidthState(newWidth.toString()); // If mouse is up, remove event listeners if (!event.buttons) { return removeListeners(); } }; window.addEventListener("mousemove", mouseMoveHandler); window.addEventListener("mouseup", removeListeners); } ); const updateWidth = (_newWidth: string) => { setWidthState(_newWidth.toString()); if (!imgRef.current) { return; } const imageParent = document.querySelector( ".ProseMirror-selectednode" ) as HTMLDivElement; const imageParentWidth = imageParent.offsetWidth; const currentWidth = imgRef.current.width; const currentHeight = imgRef.current.height; let newWidth = Number(_newWidth); newWidth = newWidth > 59 ? newWidth : 60; if (newWidth > imageParentWidth) { newWidth = imageParentWidth; } const newHeight = (newWidth / currentWidth) * currentHeight; setResizingStyle({ width: newWidth > 59 ? newWidth : 60, height: newHeight, }); }; function dragButton(direction: "left" | "right") { return (
); } const { externalLink, alt, borderRadius: _br, borderColor: _bc, isUploading, ...attrs } = node.attrs || {}; return ( {isUploading ? (
) : ( )} {selected && !isUploading && ( <> {dragButton("left")} {dragButton("right")} )}
e.preventDefault()} onCloseAutoFocus={(e) => e.preventDefault()} >
updateWidth(e.target.value)} className="border-0 focus-visible:ring-0 h-6 p-0 w-8" />
Width
{alignments.map((alignment) => ( Align {alignment} ))}
props.updateAttributes({ borderRadius: e.target.value, }) } className="border-0 focus-visible:ring-0 h-6 p-0 w-5" />
Border radius
props.updateAttributes({ borderWidth: e.target.value, }) } className="border-0 focus-visible:ring-0 h-6 p-0 w-5" />
Border width
} color={borderColor} onChange={(color) => { props.updateAttributes({ borderColor: color, }); }} /> Border color { props.updateAttributes({ src: u, }); setOpenImgSrc(false); }} /> Alt text { props.updateAttributes({ alt: t, }); setOpenAltText(false); }} /> Link { props.updateAttributes({ externalLink: u, }); setOpenLink(false); }} />
); }