Files
superwork/vscode/webview-ui/src/codeReader/useDefinitionPeek.ts
T

87 lines
3.6 KiB
TypeScript

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<HTMLElement> | null {
if (!(target instanceof Element) || inPeek(target))
return null
const link = target.closest<HTMLAnchorElement>('.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<HTMLElement> | undefined, jump: () => void } {
const [shown, setShown] = useState<ShownPeek<HTMLElement> | undefined>()
const [peeks] = useState(() => new DefinitionPeeks<HTMLElement>(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() }
}