'use client'; import { useToast } from "@/components/hooks/use-toast"; import { Button } from "@/components/ui/button"; import useCaptureEvent from "@/hooks/useCaptureEvent"; import { createPathWithQueryParams } from "@/lib/utils"; import { autoPlacement, computePosition, offset, shift, VirtualElement } from "@floating-ui/react"; import { Link2Icon } from "@radix-ui/react-icons"; import { EditorView, SelectionRange } from "@uiw/react-codemirror"; import { useCallback, useEffect, useRef } from "react"; import { resolveServerPath } from "../../api/(client)/client"; interface ContextMenuProps { view: EditorView; selection: SelectionRange; repoName: string; path: string; revisionName: string; } export const EditorContextMenu = ({ view, selection, repoName, path, revisionName, }: ContextMenuProps) => { const ref = useRef(null); const { toast } = useToast(); const captureEvent = useCaptureEvent(); useEffect(() => { if (selection.empty) { ref.current?.classList.add('hidden'); } else { ref.current?.classList.remove('hidden'); } }, [selection.empty]); useEffect(() => { if (selection.empty) { return; } const { from, to } = selection; const start = view.coordsAtPos(from); const end = view.coordsAtPos(to); if (!start || !end) { return; } const selectionElement: VirtualElement = { getBoundingClientRect: () => { const { top, left } = start; const { bottom, right } = end; return { x: left, y: top, top, bottom, left, right, width: right - left, height: bottom - top, } } } if (ref.current) { computePosition(selectionElement, ref.current, { middleware: [ offset(5), autoPlacement({ boundary: view.dom, padding: 5, allowedPlacements: ['bottom'], }), shift({ padding: 5 }) ], }).then(({ x, y }) => { if (ref.current) { ref.current.style.left = `${x}px`; ref.current.style.top = `${y}px`; } }); } }, [selection, view]); const onCopyLinkToSelection = useCallback(() => { const toLineAndColumn = (pos: number) => { const lineInfo = view.state.doc.lineAt(pos); return { line: lineInfo.number, column: pos - lineInfo.from + 1, } } const from = toLineAndColumn(selection.from); const to = toLineAndColumn(selection.to); // @note: we need to resolve the server path for /browse since // we aren't using (which normally does this for us). const basePath = `${window.location.origin}${resolveServerPath('/browse')}`; const url = createPathWithQueryParams(`${basePath}/${repoName}@${revisionName}/-/blob/${path}`, ['highlightRange', `${from?.line}:${from?.column},${to?.line}:${to?.column}`], ); navigator.clipboard.writeText(url); toast({ description: "✅ Copied link to selection", }); captureEvent('share_link_created', {}); // Reset the selection view.dispatch( { selection: { anchor: selection.to, head: selection.to, } } ) }, [captureEvent, path, repoName, selection.from, selection.to, toast, view, revisionName]); return (
) }