77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
import type { DocumentMessage } from './types'
|
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
import { pageLayout } from './pageLayout'
|
|
import { scrollOwner } from './scrollOwner'
|
|
import { postMessage, viewState } from './vscode'
|
|
|
|
/** 滚动时最多这么久记一次位置 */
|
|
const RECORD_INTERVAL_MS = 100
|
|
|
|
/**
|
|
* 筛选栏有多高,跟着它变(换行、多出第二行时):钉在它下面的东西(总览条、钉住的卡片)从这儿开始。
|
|
* 大小变了从 ResizeObserver 报来的尺寸里取,不回头读它的 offsetHeight(那要先排版)。
|
|
*/
|
|
export function usePinnedHeight(): number {
|
|
const [height, setHeight] = useState(0)
|
|
useLayoutEffect(() => {
|
|
const bar = document.querySelector<HTMLElement>('.cr-filter-bar')
|
|
if (!bar)
|
|
return
|
|
setHeight(bar.offsetHeight)
|
|
const observer = new ResizeObserver(([entry]) => setHeight(entry.borderBoxSize[0]?.blockSize ?? bar.offsetHeight))
|
|
observer.observe(bar)
|
|
return () => observer.disconnect()
|
|
}, [])
|
|
return height
|
|
}
|
|
|
|
/** 记下视口顶部那块;它的起始行变了才报给扩展端。 */
|
|
function record(reportedLine: { current: number | undefined }): void {
|
|
const anchor = pageLayout.anchorAt(window.scrollY + pageLayout.pinnedHeight())
|
|
if (!anchor)
|
|
return
|
|
viewState.update({ scroll: anchor })
|
|
if (anchor.line !== reportedLine.current) {
|
|
reportedLine.current = anchor.line
|
|
postMessage({ type: 'visibleLine', line: anchor.line })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 记住并恢复预览看到的位置:
|
|
* ① 滚动时([scrollOwner] 转来的滚动事件)把视口顶部那块记进 webview 状态,页面重新加载后(VS Code 重启后恢复标签页、webview 进程崩了重建)
|
|
* 交给 [scrollOwner] 滚回去、钉住那块,之后注释里的图画出来、中文名到了也不跑;
|
|
* ② 同时把那块的起始行报给扩展端,切回文本编辑器时把这行放到顶部。
|
|
* 第一份文档带 reveal(刚从文本编辑器切过来、跳到定义)时不恢复,交给 useReveal 滚。
|
|
* 块的位置从 pageLayout 取:滚动时用量过的,不每次量全部卡片。
|
|
*/
|
|
export function useScrollPosition(doc: DocumentMessage | null): void {
|
|
const restored = useRef(false)
|
|
const reportedLine = useRef<number | undefined>(undefined)
|
|
|
|
// 第一份文档渲染进 DOM 后、浏览器绘制前滚好,不会先闪一下顶部
|
|
useLayoutEffect(() => {
|
|
if (!doc || restored.current)
|
|
return
|
|
restored.current = true
|
|
const saved = viewState.get().scroll
|
|
if (doc.reveal === undefined && saved)
|
|
scrollOwner.align(saved)
|
|
record(reportedLine)
|
|
}, [doc])
|
|
|
|
useEffect(() => {
|
|
let timer: number | undefined
|
|
const off = scrollOwner.onScroll(() => {
|
|
timer ??= window.setTimeout(() => {
|
|
timer = undefined
|
|
record(reportedLine)
|
|
}, RECORD_INTERVAL_MS)
|
|
})
|
|
return () => {
|
|
off()
|
|
window.clearTimeout(timer)
|
|
}
|
|
}, [])
|
|
}
|