import type { PeekTarget, ShownPeek } from './definitionPeek' import { useEffect, useState } from 'react' import { DefinitionPeeks, documentNameAt, inPeek, linkPeekSource } from './definitionPeek' import { onMessage, postMessage } from './vscode' /** 指针下能弹定义卡片的一处:注释里指到符号的链接,或页面上这份文档带下标的名字;浮窗里的不算。 */ function peekTargetAt(target: EventTarget | null): PeekTarget | null { if (!(target instanceof Element) || inPeek(target)) return null const link = target.closest('.cr-markdown a') if (link) { const source = linkPeekSource(link.getAttribute('href') ?? '') return source ? { anchor: link, source } : null } const name = documentNameAt(target) return name?.dataset.offset === undefined ? null : { anchor: name, source: { offset: Number(name.dataset.offset) } } } function isInPeek(target: EventTarget | null): boolean { return target instanceof Element && inPeek(target) } /** * 和 VS Code 编辑器里的悬停一样:鼠标停在带下标的名字、注释里指到符号的链接上,过一会儿(editor.hover.delay)弹出定义所在那块的卡片, * 移开名字和浮窗过一会儿(editor.hover.hidingDelay)收起,Esc、滚动、按下鼠标立刻收(见 DefinitionPeeks)。 * version 是当前显示的文档版本,名字的下标属于它。返回弹着的卡片和点标题时的跳转。 */ export function useDefinitionPeek(version: number | undefined): { shown: ShownPeek | undefined, jump: () => void } { const [shown, setShown] = useState | undefined>() const [peeks] = useState(() => new DefinitionPeeks(postMessage, setShown)) useEffect(() => { peeks.setVersion(version) }, [peeks, version]) useEffect(() => { const onOver = (event: PointerEvent): void => { if (isInPeek(event.target)) peeks.enterPopup() else peeks.point(peekTargetAt(event.target)) } const onOut = (event: PointerEvent): void => { if (event.relatedTarget === null) peeks.point(null) } const onDown = (event: PointerEvent): void => { if (!isInPeek(event.target)) peeks.dismiss() } const onKey = (event: KeyboardEvent): void => { if (event.key === 'Escape') peeks.dismiss() } // 捕获阶段:页面和页面里能滚的框滚动都收;浮窗自己里面滚不收 const onScroll = (event: Event): void => { if (!isInPeek(event.target)) peeks.dismiss() } // hoverDelay 在页面 ready 后、第一份文档之前就来,挂在页面根上的 App 一开始就收 const off = onMessage((msg) => { if (msg.type === 'hoverDelay') peeks.setDelays(msg.delay, msg.hidingDelay) else if (msg.type === 'definitionPeeked') peeks.answer(msg.id, msg.peek) else if (msg.type === 'reveal') peeks.dismiss() }) document.addEventListener('pointerover', onOver) document.addEventListener('pointerout', onOut) document.addEventListener('pointerdown', onDown, true) window.addEventListener('keydown', onKey) window.addEventListener('scroll', onScroll, true) return () => { off() document.removeEventListener('pointerover', onOver) document.removeEventListener('pointerout', onOut) document.removeEventListener('pointerdown', onDown, true) window.removeEventListener('keydown', onKey) window.removeEventListener('scroll', onScroll, true) peeks.dispose() } }, [peeks]) return { shown, jump: () => peeks.jump() } }