🐛 fix(vscode): 代码阅读按 ⌘↓ 跳高亮不再来回闪,筛选栏加上上一处、下一处
This commit is contained in:
1 parent
70a53e5a68
commit
b16cc6409e
8 files changed
+221
-36
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import type { Click, Highlight, ScrollPosition } from './occurrences'
|
||||
import type { BlockView, Occurrence } from './types'
|
||||
import { createContext, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { clickAction, KeyScroll, OccurrenceHighlight, OccurrenceNavigation } from './occurrences'
|
||||
import { createContext, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { clickAction, KeyScroll, OccurrenceHighlight, occurrenceJumpKey, OccurrenceNavigation } from './occurrences'
|
||||
import { occurrencePlan } from './reveal'
|
||||
import { showInViewport } from './useReveal'
|
||||
import { onMessage, postMessage } from './vscode'
|
||||
@@ -52,29 +52,41 @@ function scrollPosition(): ScrollPosition {
|
||||
return { x: window.scrollX, y: window.scrollY }
|
||||
}
|
||||
|
||||
/** mac 用 ⌘,别的系统用 ctrl。页面跑在 VS Code 的网页里,平台从浏览器读。 */
|
||||
function isMac(): boolean {
|
||||
return /Mac|iPhone|iPad/.test(navigator.platform)
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻滚到这里,顺带打断浏览器正在播(或刚排上、还没开始播)的滚动动画。滚到原地浏览器当没滚、不打断动画,
|
||||
* 所以先挪一个像素再回来:同一段脚本里连着滚两下,中间不画,画面上看不出。
|
||||
* 这两处位置先交给 own:滚动事件可能晚于这次调用才到,不能被当成浏览器把页面滚走。
|
||||
*/
|
||||
function scrollBack({ x, y }: ScrollPosition): void {
|
||||
window.scrollTo({ left: x, top: y > 0 ? y - 1 : y + 1, behavior: 'instant' })
|
||||
function scrollBack({ x, y }: ScrollPosition, own: (position: ScrollPosition) => void): void {
|
||||
const nudged = { x, y: y > 0 ? y - 1 : y + 1 }
|
||||
own(nudged)
|
||||
own({ x, y })
|
||||
window.scrollTo({ left: x, top: nudged.y, behavior: 'instant' })
|
||||
window.scrollTo({ left: x, top: y, behavior: 'instant' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 单击名字高亮它在文档里的各处出现,点空白处、Esc、换文档版本或翻译清掉;返回眼下高亮着的名字。
|
||||
* 高亮着时扩展端的「跳到下一处 / 上一处高亮」命令发来 moveOccurrence:当前那一处换成下一处页面上找得到的出现,
|
||||
* 展开把它折叠住的块、属性行和子行,再把它滚进视口。
|
||||
* 触发命令的那个键浏览器自己的滚动不拦(页面不知道是哪个键),跳的时候盖掉它,见 KeyScroll。
|
||||
* 单击名字高亮它在文档里的各处出现,点空白处、Esc、换文档版本或翻译清掉;返回眼下高亮着的名字,和筛选栏上「上一处 / 下一处」用的跳转。
|
||||
* 高亮着时扩展端的「跳到下一处 / 上一处高亮」命令发来 moveOccurrence,筛选栏上的按钮走同一条跳转:当前那一处换成下一处页面上找得到的出现,
|
||||
* 展开把它折叠住的块、属性行和子行,再把它滚进视口。按钮不是按键,不按 ⌘↓ 那样拉回按键时的位置。
|
||||
* 默认的 ⌘↓ / ⌘↑(Windows 是 ctrl)在能跳的时候取消浏览器滚到底 / 滚到顶,但不停事件继续传(VS Code 在后面才把按键收成命令)。
|
||||
* 别的键浏览器仍可能自己滚,跳的时候盖掉它,见 KeyScroll。
|
||||
*/
|
||||
export function useOccurrences(version: number | undefined, translatorId: string | undefined, page: OccurrencePage | undefined): Highlight | undefined {
|
||||
export function useOccurrences(version: number | undefined, translatorId: string | undefined, page: OccurrencePage | undefined): { highlight: Highlight | undefined, moveOccurrence: (step: 1 | -1) => void } {
|
||||
const [highlight, setHighlight] = useState<Highlight | undefined>()
|
||||
const [occurrences] = useState(() => new OccurrenceHighlight(postMessage, setHighlight))
|
||||
const [navigation] = useState(() => new OccurrenceNavigation(postMessage, { focused: document.hasFocus(), typing: isTyping(document.activeElement) }))
|
||||
const [keyScroll] = useState(() => new KeyScroll())
|
||||
/** 用键盘跳过几次:每跳一次,画完后把当前那一处滚进视口 */
|
||||
/** 用键盘或筛选栏按钮跳过几次:每跳一次,画完后把当前那一处滚进视口 */
|
||||
const [moves, setMoves] = useState(0)
|
||||
const latestPage = useRef(page)
|
||||
/** 筛选栏按钮调用的跳转。真正的函数在下面的效果里,挂上监听时才有。 */
|
||||
const moveFromButton = useRef<(step: 1 | -1) => void>(() => {})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
latestPage.current = page
|
||||
@@ -91,16 +103,22 @@ export function useOccurrences(version: number | undefined, translatorId: string
|
||||
return
|
||||
const el = document.querySelector<HTMLElement>('[data-current]')
|
||||
if (el)
|
||||
showInViewport(el)
|
||||
showInViewport(el, position => keyScroll.own(position))
|
||||
keyScroll.hold(scrollPosition())
|
||||
}, [moves, keyScroll])
|
||||
|
||||
useEffect(() => {
|
||||
const move = (step: 1 | -1): void => {
|
||||
// 触发命令的那个键浏览器可能已经照自己的意思滚了(⌘↓ 滚到底),先回到按键那一刻
|
||||
const before = keyScroll.take(performance.now())
|
||||
if (before)
|
||||
scrollBack(before)
|
||||
const move = (step: 1 | -1, fromKey: boolean): void => {
|
||||
if (fromKey) {
|
||||
// 触发命令的那个键浏览器可能已经照自己的意思滚了(⌘↓ 滚到底),先回到按键那一刻
|
||||
const before = keyScroll.take(performance.now())
|
||||
if (before)
|
||||
scrollBack(before, position => keyScroll.own(position))
|
||||
}
|
||||
else {
|
||||
// 筛选栏按钮不是按键,别把这次滚动当成 ⌘↓ 拉回按键时的位置
|
||||
keyScroll.forget()
|
||||
}
|
||||
const current = latestPage.current
|
||||
const planOf = (occurrence: Occurrence): ReturnType<typeof occurrencePlan> => current && occurrencePlan(current.blocks, occurrence, current.skip)
|
||||
const target = current && occurrences.move(step, occurrence => planOf(occurrence) !== undefined)
|
||||
@@ -116,13 +134,18 @@ export function useOccurrences(version: number | undefined, translatorId: string
|
||||
}
|
||||
// 捕获阶段:赶在块里的按钮处理点击、折叠箭头换图标之前读出点在哪
|
||||
const onClick = (event: MouseEvent): void => occurrences.click(clickAction(clickOf(event)))
|
||||
// 任何键都照常交给浏览器和 VS Code,这里只记下按键那一刻页面滚在哪
|
||||
const onKeyDown = (event: KeyboardEvent): void => keyScroll.press(scrollPosition(), event.timeStamp)
|
||||
moveFromButton.current = step => move(step, false)
|
||||
// 任何键都照常交给 VS Code。能跳时,默认的 ⌘↓ / ⌘↑(Windows 是 ctrl)取消浏览器滚到底 / 滚到顶,但不停冒泡,命令还是收得到
|
||||
const onKeyDown = (event: KeyboardEvent): void => {
|
||||
if (navigation.available && occurrenceJumpKey(event, isMac()))
|
||||
event.preventDefault()
|
||||
keyScroll.press(scrollPosition(), event.timeStamp)
|
||||
}
|
||||
const onPointer = (): void => keyScroll.forget()
|
||||
const onScroll = (event: Event): void => {
|
||||
const back = keyScroll.scrolled(scrollPosition(), event.timeStamp)
|
||||
if (back)
|
||||
scrollBack(back)
|
||||
scrollBack(back, position => keyScroll.own(position))
|
||||
}
|
||||
const onKey = (event: KeyboardEvent): void => {
|
||||
// 输入框、下拉列表自己用掉的 Esc 不算
|
||||
@@ -137,7 +160,7 @@ export function useOccurrences(version: number | undefined, translatorId: string
|
||||
if (msg.type === 'occurrencesFound')
|
||||
occurrences.answer(msg.id, msg.occurrences)
|
||||
else if (msg.type === 'moveOccurrence')
|
||||
move(msg.step)
|
||||
move(msg.step, true)
|
||||
})
|
||||
navigation.update({})
|
||||
document.addEventListener('click', onClick, true)
|
||||
@@ -165,5 +188,6 @@ export function useOccurrences(version: number | undefined, translatorId: string
|
||||
}
|
||||
}, [occurrences, navigation, keyScroll])
|
||||
|
||||
return highlight
|
||||
const moveOccurrence = useCallback((step: 1 | -1) => moveFromButton.current(step), [])
|
||||
return { highlight, moveOccurrence }
|
||||
}
|
||||
Reference in new issue
Block a user