87 lines
3.6 KiB
TypeScript
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() }
|
|
}
|