import { useCallback, useId, useLayoutEffect, useRef, useState } from "react"; import { FloatingPortal } from "@floating-ui/react"; import { NodeViewWrapper, NodeViewContent } from "@tiptap/react"; import type { NodeViewProps } from "@tiptap/react"; import { CODE_LANGUAGES, resolveLanguage } from "@/shared/editor/schema/code-block"; import { CodeBlockCopyButtonChrome, getCodeCopyButtonLabel, getCodeLanguageLabel, } from "@/shared/editor/presentation/code-block"; import { preserveEditorSelectionOnMouseDown, useEditorPopover } from "../../controllers/menu/popover"; import { useEditorAffordance } from "../../editor-affordance-context"; import { useCopyFeedback } from "@/shared/browser/use-copy-feedback"; import "@/styles/editor/code-block.css"; export function CodeBlockView({ node, updateAttributes }: NodeViewProps) { const { documentEditable } = useEditorAffordance(); const [open, setOpen] = useState(false); const btnRef = useRef(null); const dropdownRef = useRef(null); const activeItemRef = useRef(null); const copyId = useId(); const { copiedId, copy } = useCopyFeedback(); const isCopied = copiedId === copyId; const language = resolveLanguage(node.attrs.language); const displayName = getCodeLanguageLabel(language); const { floatingStyles, getFloatingProps, refs } = useEditorPopover({ open, onClose: () => setOpen(false), anchorRef: btnRef, placement: "bottom-end", offset: 4, padding: 8, }); const selectLanguage = useCallback( (lang: string) => { updateAttributes({ language: lang }); setOpen(false); }, [updateAttributes], ); useLayoutEffect(() => { if (!open) return; let frameId = 0; frameId = window.requestAnimationFrame(() => { const dropdown = dropdownRef.current; const activeItem = activeItemRef.current; if (!dropdown || !activeItem) return; const maxScrollTop = Math.max(0, dropdown.scrollHeight - dropdown.clientHeight); const targetScrollTop = activeItem.offsetTop - (dropdown.clientHeight - activeItem.offsetHeight) / 2; dropdown.scrollTop = Math.min(maxScrollTop, Math.max(0, targetScrollTop)); }); return () => window.cancelAnimationFrame(frameId); }, [language, open]); const controls = (
copy(copyId, node.textContent)} onMouseDown={(e) => e.preventDefault()} aria-label={getCodeCopyButtonLabel(isCopied)} />
); return ( {controls} {open && (
{ refs.setFloating(node); }} className="tiptap-menu-surface" role="menu" aria-label="Code block language" style={{ ...floatingStyles, width: 160, zIndex: 50, animation: "var(--animate-menu)" }} {...getFloatingProps({ onMouseDownCapture: (e) => preserveEditorSelectionOnMouseDown(e), })} >
{Object.entries(CODE_LANGUAGES).map(([id, meta]) => ( ))}
)}
         as="code" className="tiptap-code-block-content" style={{ whiteSpace: "inherit" }} />
      
); }