✨ feat(vscode): 注释里的仓库路径链接能点开并跳到符号
This commit is contained in:
1 parent
5528a405cc
commit
c4113748e1
13 files changed
+662
-18
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { RepoLinks } from './repoLink'
|
||||
import { onMessage, postMessage } from './vscode'
|
||||
|
||||
/** 坏链接悬停上的字,走页面上统一的 data-tooltip,不用原生 title。 */
|
||||
const MISSING = '找不到'
|
||||
|
||||
/** 文件或符号没有的仓库链接:红色波浪线,悬停「找不到」。问到之前先是普通链接。 */
|
||||
function paintLink(anchor: HTMLAnchorElement, broken: boolean): void {
|
||||
const tip = anchor.getAttribute('data-tooltip')
|
||||
if (broken) {
|
||||
anchor.classList.add('cr-link-broken')
|
||||
if (tip !== MISSING) {
|
||||
if (tip)
|
||||
anchor.dataset.linkTooltip = tip
|
||||
anchor.setAttribute('data-tooltip', MISSING)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!anchor.classList.contains('cr-link-broken'))
|
||||
return
|
||||
anchor.classList.remove('cr-link-broken')
|
||||
if (tip === MISSING) {
|
||||
const prev = anchor.dataset.linkTooltip
|
||||
if (prev)
|
||||
anchor.setAttribute('data-tooltip', prev)
|
||||
else
|
||||
anchor.removeAttribute('data-tooltip')
|
||||
}
|
||||
delete anchor.dataset.linkTooltip
|
||||
}
|
||||
|
||||
function paint(links: RepoLinks): void {
|
||||
for (const anchor of document.querySelectorAll<HTMLAnchorElement>('.cr-markdown a'))
|
||||
paintLink(anchor, links.isBroken(anchor.getAttribute('href') ?? ''))
|
||||
}
|
||||
|
||||
/**
|
||||
* 注释、说明、问 AI 的回答里的仓库路径链接:点了发给扩展端打开(不走 webview 把 http 链接交给外部打开的那条),
|
||||
* 画出来之后把路径发去问哪些文件没有。
|
||||
*/
|
||||
export function useRepoLinks(): void {
|
||||
const [links] = useState(() => new RepoLinks(postMessage))
|
||||
|
||||
useEffect(() => {
|
||||
const off = onMessage((msg) => {
|
||||
if (msg.type === 'repoLinksChecked') {
|
||||
if (links.checked(msg.id, msg.missing))
|
||||
paint(links)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'repoLinkBroken') {
|
||||
links.broken(msg.path, msg.symbol)
|
||||
paint(links)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'repoLinkOpened') {
|
||||
links.opened(msg.path, msg.symbol)
|
||||
paint(links)
|
||||
}
|
||||
})
|
||||
// 捕获阶段:赶在 webview 把 <a> 交给外部打开之前拦下仓库链接
|
||||
const onClick = (event: MouseEvent): void => {
|
||||
const anchor = event.target instanceof Element ? event.target.closest('a') : null
|
||||
if (!anchor || !links.click(anchor.getAttribute('href')))
|
||||
return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
let timer = 0
|
||||
const scan = (): void => {
|
||||
window.clearTimeout(timer)
|
||||
timer = window.setTimeout(() => {
|
||||
const anchors = [...document.querySelectorAll<HTMLAnchorElement>('.cr-markdown a')]
|
||||
for (const anchor of anchors)
|
||||
paintLink(anchor, links.isBroken(anchor.getAttribute('href') ?? ''))
|
||||
links.scan(anchors.map(anchor => anchor.getAttribute('href') ?? ''))
|
||||
}, 50)
|
||||
}
|
||||
const observer = new MutationObserver(scan)
|
||||
observer.observe(document.body, { childList: true, subtree: true })
|
||||
document.addEventListener('click', onClick, true)
|
||||
document.addEventListener('auxclick', onClick, true)
|
||||
scan()
|
||||
return () => {
|
||||
off()
|
||||
observer.disconnect()
|
||||
document.removeEventListener('click', onClick, true)
|
||||
document.removeEventListener('auxclick', onClick, true)
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}, [links])
|
||||
}
|
||||
Reference in new issue
Block a user