| import { NodeViewWrapper } from "@tiptap/react"; |
| import { useState, useRef, useCallback } from "react"; |
| import type { NodeViewProps } from "@tiptap/react"; |
|
|
| export function CitationView({ node, editor, getPos }: NodeViewProps) { |
| const key = node.attrs.key as string; |
| const label = (node.attrs.label as string) || `[${key}]`; |
| const [showTooltip, setShowTooltip] = useState(false); |
| const tooltipTimer = useRef<ReturnType<typeof setTimeout>>(); |
| const wrapperRef = useRef<HTMLSpanElement>(null); |
|
|
| const getEntry = useCallback(() => { |
| return (editor.storage.citation as any)?.citationsMap?.get(key) ?? null; |
| }, [editor, key]); |
|
|
| const handleMouseEnter = useCallback(() => { |
| tooltipTimer.current = setTimeout(() => setShowTooltip(true), 300); |
| }, []); |
|
|
| const handleMouseLeave = useCallback(() => { |
| clearTimeout(tooltipTimer.current); |
| setShowTooltip(false); |
| }, []); |
|
|
| const removeCitation = useCallback(() => { |
| const pos = getPos(); |
| if (typeof pos === "number") { |
| editor.chain().focus().deleteRange({ from: pos, to: pos + 1 }).run(); |
| } |
| setShowTooltip(false); |
| }, [editor, getPos]); |
|
|
| const entry = showTooltip ? getEntry() : null; |
|
|
| return ( |
| <NodeViewWrapper |
| as="span" |
| className="citation-node" |
| ref={wrapperRef} |
| onMouseEnter={handleMouseEnter} |
| onMouseLeave={handleMouseLeave} |
| > |
| {label} |
| {showTooltip && entry && ( |
| <div className="citation-tooltip"> |
| <div className="citation-tooltip-title">{entry.title || key}</div> |
| {entry["container-title"] && ( |
| <div className="citation-tooltip-journal"> |
| {entry["container-title"]} |
| {entry.volume ? `, ${entry.volume}` : ""} |
| {entry.page ? `, ${entry.page}` : ""} |
| </div> |
| )} |
| {entry.DOI && ( |
| <div className="citation-tooltip-doi">doi: {entry.DOI}</div> |
| )} |
| <button |
| className="citation-tooltip-remove" |
| onMouseDown={(e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| removeCitation(); |
| }} |
| > |
| Remove |
| </button> |
| </div> |
| )} |
| </NodeViewWrapper> |
| ); |
| } |
|
|