🐛 fix(vscode): 代码阅读的钉住卡片改成每张卡片都算一层,折叠着、没有子块的高卡片滚到一半也钉在顶上
This commit is contained in:
1 parent
a53a5d4504
commit
a1fbefd3aa
14 files changed
+45
-34
No files matched your search
@@ -64,7 +64,7 @@ export function App(): ReactElement | null {
|
||||
const [diff, setDiff] = useState<DocumentDiff | null>(null)
|
||||
/** 收到消息时读眼下哪些块在编辑(消息回调只在挂载时注册一次) */
|
||||
const editing = useRef(view.editing)
|
||||
/** 块树所在的页面内容:钉住外层卡片时在这里面量 */
|
||||
/** 块树所在的页面内容:钉住卡片时在这里面量 */
|
||||
const content = useRef<HTMLDivElement>(null)
|
||||
// 解析失败的文档没有块,不标差异;删掉的块按块筛选同样藏
|
||||
const changes = useMemo(() => doc && doc.error === undefined
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('挂在卡片子行上的子块', () => {
|
||||
expect(render({ '0.0': false, '0/p1': true })).not.toContain('启动服务')
|
||||
})
|
||||
|
||||
it('画出来的子层记着它是哪张卡片的(钉住外层卡片时按它认):子行下面那层、卡片后面那层都是;收着时没有', () => {
|
||||
it('画出来的子层记着它是哪张卡片的(钉住卡片时按它算卡片管到哪):子行下面那层、卡片后面那层都是;收着时没有', () => {
|
||||
expect(render({})).not.toContain('data-owner')
|
||||
expect(render({ '0.0': false, '0': false }).match(/data-owner="0"/g)).toHaveLength(2)
|
||||
})
|
||||
|
||||
@@ -129,7 +129,7 @@ function kindProps(block: BlockView | undefined): { 'data-kind'?: string, 'data-
|
||||
|
||||
/**
|
||||
* 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。被筛掉、被盖住的块不画,不留空位,竖线跟着变短。
|
||||
* owner 是这一层所属的块(顶层没有,记在 data-owner 上,钉住外层卡片时按它认哪张卡片的子块画出来了),悬停它时竖线换成它的种类色。
|
||||
* owner 是这一层所属的块(顶层没有,记在 data-owner 上,钉住卡片时按它算卡片连同子块管到哪、套着几层),悬停它时竖线换成它的种类色。
|
||||
* 标差异时每张卡片左边外侧一条改动色条,删掉的块收成一行放回原来的位置(见 RemovedCard);「只看改动」时连着没变的块折成一条(UnchangedStrip)。
|
||||
* slotted:这一层是画在卡片属性表某一行下面的子块(BlockSlot),blocks 只有那几块。
|
||||
*/
|
||||
|
||||
@@ -41,10 +41,10 @@ export function PinnedHead({ block }: { block: BlockView }): ReactElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* 钉在顶上的外层卡片(同 VS Code 编辑器的 Sticky Scroll,设置也用它的):滚进一张卡片的子块、卡片自己的开头已经滚到筛选栏后面时,
|
||||
* 筛选栏下面钉一行这张卡片的头(PinnedHead),外层的在上、里层的往下摞,每行和那张卡片左右对齐(越里层越往右);
|
||||
* 这张卡片的子块快滚完时,最里那行被下面的内容顶上去、从上一行下面滑走。哪几张见 pinnedLines,只按页面上画出来的算:
|
||||
* 折叠着的、被筛掉的、正在编辑的卡片下面没有子块,不钉。点一行把那张卡片滚到顶上(外层还钉着的几行下面)、闪几下。
|
||||
* 钉在顶上的卡片(同 VS Code 编辑器的 Sticky Scroll,设置也用它的,一张卡片就是一层):卡片的开头已经滚到筛选栏后面、卡片(连同子块)
|
||||
* 还有一部分看得见时,筛选栏下面钉一行这张卡片的头(PinnedHead),外层的在上、里层的往下摞,每行和那张卡片左右对齐(越里层越往右);
|
||||
* 这张卡片快滚完时,最里那行被下面的内容顶上去、从上一行下面滑走。哪几张见 pinnedLines,只按页面上画出来的算:
|
||||
* 折叠着的卡片自己照样钉(它的子块没画出来,不算);被筛掉的不在页面上,正在编辑的不钉。点一行把那张卡片滚到顶上(外层还钉着的几行下面)、闪几下。
|
||||
* 钉着的名字不画页面上的高亮(页面上找当前那一处只认卡片里的);跳到定义、悬停看卡片照旧。
|
||||
* content 是块树所在的页面内容,blocks 是这份文档的块。
|
||||
*/
|
||||
|
||||
@@ -1248,7 +1248,7 @@ del.cr-diff-word {
|
||||
}
|
||||
|
||||
/*
|
||||
* 钉在顶上的外层卡片(StickyLines.tsx,同 VS Code 编辑器的 Sticky Scroll):从筛选栏下沿往下摞,底色、悬停色、下沿的阴影同编辑器的
|
||||
* 钉在顶上的卡片(StickyLines.tsx,同 VS Code 编辑器的 Sticky Scroll):从筛选栏下沿往下摞,底色、悬停色、下沿的阴影同编辑器的
|
||||
* (--vscode-editorStickyScroll*,主题没给时用编辑器底色、控件阴影)。最里那行被顶上去时从上一行下面滑走(层级低一级),整块跟着变矮。
|
||||
* 层级在页面内容之上,低于总览条(5)、筛选栏(10)和它的下拉菜单、定义卡片浮窗(40)、下拉列表和悬停提示(50)
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,12 @@ describe('pinnedLines:钉住哪几张卡片', () => {
|
||||
expect(ids(pinnedLines([scope('3', 0, -1, 500)], LINE, 5))).toEqual(['3'])
|
||||
})
|
||||
|
||||
it('没有子块的高卡片(折叠着的函数,下沿就是卡片的):名称那行滚到后面、下面的行还看得见就钉,卡片滚完时被顶上去、再滚就不钉', () => {
|
||||
expect(pinnedLines([scope('8', 0, -120, 180)], LINE, 5)).toEqual({ scopes: [scope('8', 0, -120, 180)], offset: 0 })
|
||||
expect(pinnedLines([scope('8', 0, -290, 10)], LINE, 5)).toMatchObject({ offset: -14 })
|
||||
expect(ids(pinnedLines([scope('8', 0, -310, -10), scope('9', 0, -2, 300)], LINE, 5))).toEqual(['9'])
|
||||
})
|
||||
|
||||
it('套着的几层:外层在上、里层在下,各在自己那一行;开头还没滚过它那一行的不钉', () => {
|
||||
const scopes = [scope('2', 0, -300, 800), scope('2.1', 1, -100, 400), scope('2.1.0', 2, 60, 300)]
|
||||
expect(pinnedLines(scopes, LINE, 5)).toEqual({ scopes: scopes.slice(0, 2), offset: 0 })
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* 钉在顶上的外层卡片(同 VS Code 编辑器的 Sticky Scroll):滚进一张卡片的子块、卡片自己的开头已经滚出去时,顶上钉一行它的图标和标题,
|
||||
* 外层的在上、里层的往下摞,每行一样高。竖直坐标都以筛选栏下沿为 0、往下为正(像素)。
|
||||
* 钉在顶上的卡片(同 VS Code 编辑器的 Sticky Scroll,一张卡片就是一层):卡片的开头已经滚出去、卡片(连同子块)还有一部分看得见时,
|
||||
* 顶上钉一行它的图标和标题,外层的在上、里层的往下摞,每行一样高。竖直坐标都以筛选栏下沿为 0、往下为正(像素)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 页面上一张能钉住的卡片:子块画出来了的(折叠着的、被筛掉的、正在编辑的卡片下面没有)。
|
||||
* top 是卡片上沿,bottom 是它子块最后的下沿(卡片比子块还靠下时取卡片的);depth 是它外面套着几张卡片;left、right 是卡片左右边(视口里的 x)
|
||||
* 页面上一张能钉住的卡片:画出来了、不在编辑的(折叠着的、没有子块的也算;被筛掉的、折起来的子块没画出来,不算)。
|
||||
* top 是卡片上沿,bottom 是卡片连同画出来的子块的下沿;depth 是它外面套着几张卡片;left、right 是卡片左右边(视口里的 x)
|
||||
*/
|
||||
export interface StickyScope {
|
||||
id: string
|
||||
@@ -26,7 +26,7 @@ export const NO_PINNED: PinnedLines = { scopes: [], offset: 0 }
|
||||
|
||||
/**
|
||||
* 钉住哪几张,照 VS Code 的 `StickyScrollController.findScrollWidgetState`:按页面先后(外层在前)看每张,它那一行在 depth × lineHeight;
|
||||
* 卡片上沿已经在这一行顶上面、子块的下沿还没过这一行顶时钉住。子块的下沿高过这一行的下沿时,这一行跟着往上推。
|
||||
* 卡片上沿已经在这一行顶上面、下沿(连同子块)还没过这一行顶时钉住。下沿高过这一行的下沿时,这一行跟着往上推。
|
||||
* 钉满 limit 行就不再往里看:套得深时留外层的,同 VS Code。scopes 要按页面先后排。
|
||||
*/
|
||||
export function pinnedLines(scopes: readonly StickyScope[], lineHeight: number, limit: number): PinnedLines {
|
||||
|
||||
@@ -409,7 +409,7 @@ export interface DocumentDiff {
|
||||
}
|
||||
|
||||
/**
|
||||
* VS Code 的 Sticky Scroll 设置(editor.stickyScroll.enabled / editor.stickyScroll.maxLineCount):预览滚进一张卡片的子块时把它钉在顶上,
|
||||
* VS Code 的 Sticky Scroll 设置(editor.stickyScroll.enabled / editor.stickyScroll.maxLineCount):卡片的开头滚到预览顶上后面、卡片还有一部分看得见时把它钉在顶上,
|
||||
* 外层的在上、里层的在下,最多钉 maxLineCount 行。
|
||||
*/
|
||||
export interface StickyScrollSettings {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function flash(el: HTMLElement): void {
|
||||
el.setAttribute('data-flash', '')
|
||||
}
|
||||
|
||||
/** 把 el 滚进钉住的筛选栏和外层卡片下面的视口。已经整个看得见就不滚。beforeScroll 在真正滚动前拿到将要落到的位置。 */
|
||||
/** 把 el 滚进钉住的筛选栏和卡片下面的视口。已经整个看得见就不滚。beforeScroll 在真正滚动前拿到将要落到的位置。 */
|
||||
export function showInViewport(el: HTMLElement, beforeScroll?: (position: ScrollPosition) => void): void {
|
||||
const rect = el.getBoundingClientRect()
|
||||
const pinned = pinnedHeight() + stickyHeight()
|
||||
|
||||
@@ -12,7 +12,7 @@ export function pinnedHeight(): number {
|
||||
return document.querySelector<HTMLElement>('.cr-filter-bar')?.offsetHeight ?? 0
|
||||
}
|
||||
|
||||
/** 筛选栏有多高,跟着它变(换行、多出第二行时):钉在它下面的东西(总览条、钉住的外层卡片)从这儿开始。 */
|
||||
/** 筛选栏有多高,跟着它变(换行、多出第二行时):钉在它下面的东西(总览条、钉住的卡片)从这儿开始。 */
|
||||
export function usePinnedHeight(): number {
|
||||
const [height, setHeight] = useState(0)
|
||||
useLayoutEffect(() => {
|
||||
|
||||
@@ -22,8 +22,12 @@ function limitOf(maxLineCount: number): number {
|
||||
return lineLimit(window.innerHeight - pinnedHeight(), STICKY_LINE_HEIGHT, maxLineCount)
|
||||
}
|
||||
|
||||
/** root 里能钉住的卡片:画出了子层的那些(子层带 data-owner,挂在子行上的子块那层在卡片里面),按页面先后;竖直坐标以筛选栏下沿为 0 */
|
||||
function measureScopes(root: HTMLElement): StickyScope[] {
|
||||
/**
|
||||
* root 里能钉住的卡片,按页面先后,竖直坐标以筛选栏下沿为 0:画出来了、不在编辑的每一张(BlockCard 带 data-line;删掉的块那张没有),
|
||||
* 折叠着的、没有子块的也算,卡片自己就是一层。下沿算到它画出来的子块的最后(它名下的子层带 data-owner,挂在子行上的子块那层在卡片里面)。
|
||||
* 只留可能钉住的:上沿在钉住的那几行(高 band)以内或更高、下沿还在筛选栏下沿以下
|
||||
*/
|
||||
function measureScopes(root: HTMLElement, band: number): StickyScope[] {
|
||||
const bottoms = new Map<string, number>()
|
||||
for (const level of root.querySelectorAll<HTMLElement>('.cr-level[data-owner]')) {
|
||||
const owner = level.dataset.owner ?? ''
|
||||
@@ -31,13 +35,13 @@ function measureScopes(root: HTMLElement): StickyScope[] {
|
||||
}
|
||||
const base = pinnedHeight()
|
||||
const scopes: StickyScope[] = []
|
||||
for (const card of root.querySelectorAll<HTMLElement>('.cr-block[data-id]')) {
|
||||
for (const card of root.querySelectorAll<HTMLElement>('.cr-block[data-line]:not([data-editing])')) {
|
||||
const id = card.dataset.id ?? ''
|
||||
const levelBottom = bottoms.get(id)
|
||||
if (levelBottom === undefined)
|
||||
continue
|
||||
const rect = card.getBoundingClientRect()
|
||||
scopes.push({ id, depth: depthOf(card), top: rect.top - base, bottom: Math.max(rect.bottom, levelBottom) - base, left: rect.left, right: rect.right })
|
||||
const top = rect.top - base
|
||||
const bottom = Math.max(rect.bottom, bottoms.get(id) ?? rect.bottom) - base
|
||||
if (top < band && bottom >= 0)
|
||||
scopes.push({ id, depth: depthOf(card), top, bottom, left: rect.left, right: rect.right })
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
@@ -65,7 +69,8 @@ export function useStickyLines(content: RefObject<HTMLElement | null>, maxLineCo
|
||||
const update = (): void => {
|
||||
frame = 0
|
||||
const root = content.current
|
||||
const next = root ? pinnedLines(measureScopes(root), STICKY_LINE_HEIGHT, limitOf(maxLineCount)) : NO_PINNED
|
||||
const limit = limitOf(maxLineCount)
|
||||
const next = root ? pinnedLines(measureScopes(root, limit * STICKY_LINE_HEIGHT), STICKY_LINE_HEIGHT, limit) : NO_PINNED
|
||||
setLines(prev => samePinned(prev, next) ? prev : next)
|
||||
}
|
||||
schedule.current = () => {
|
||||
|
||||
Reference in new issue
Block a user