Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions app/src/layout/dock/Outline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class Outline extends Model {
public blockId: string;
public isPreview: boolean;
private preFilterExpandIds: string[] | null = null;
private scrollAnimationId: number | null = null;
private scrollLastFrameTime: number = 0;
private scrollCurrentFPS: number = 60;

constructor(options: {
app: App,
Expand Down Expand Up @@ -380,11 +383,68 @@ export class Outline extends Model {
});
return;
}
// 检查是否在滚动边界区域
if (moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB || moveEvent.clientY > contentRect.bottom - Constants.SIZE_SCROLL_TB) {
this.element.scroll({
top: this.element.scrollTop + (moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB ? -Constants.SIZE_SCROLL_STEP : Constants.SIZE_SCROLL_STEP),
behavior: "smooth"
});
// 如果还没有开始滚动,则开始持续滚动
if (!this.scrollAnimationId) {
Comment on lines +388 to +389
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The logic for checking scroll boundaries and starting animation is nested deeply within the existing drag logic. Consider extracting the scroll animation logic into a separate private method to improve code organization and readability.

Copilot uses AI. Check for mistakes.
const scrollDirection = moveEvent.clientY < contentRect.top + Constants.SIZE_SCROLL_TB ? -1 : 1;
this.scrollLastFrameTime = performance.now();
let scrollFrameCount = 0;

const scrollAnimation = (currentTime: number) => {
if (!this.scrollAnimationId) {
return;
}

// 每隔 20 帧重新计算一次帧率
if (scrollFrameCount % 20 === 0) {
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The magic number 20 for frame calculation interval should be extracted as a named constant to improve code maintainability and make the logic clearer.

Copilot uses AI. Check for mistakes.
const deltaTime = currentTime - this.scrollLastFrameTime;
this.scrollLastFrameTime = currentTime;
// 计算过去 20 帧的平均帧率
this.scrollCurrentFPS = deltaTime > 0 ? (20 * 1000) / deltaTime : 60;
}
scrollFrameCount++;

// 基于当前帧率计算滚动步长,确保等效于 60fps 时的 16px/帧
const baseScrollStep = 16;
const targetFPS = 60;
Comment on lines +408 to +410
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Magic numbers 16 (scroll step) and 60 (target FPS) should be extracted as named constants, possibly as class properties or module constants, to improve maintainability.

Copilot uses AI. Check for mistakes.
const scrollStep = baseScrollStep * (targetFPS / this.scrollCurrentFPS);

this.element.scroll({
top: this.element.scrollTop + scrollStep * scrollDirection
});

// 使用 requestAnimationFrame 继续动画
this.scrollAnimationId = requestAnimationFrame(scrollAnimation);
};

// 检查浏览器是否支持 requestAnimationFrame
if (typeof requestAnimationFrame !== "undefined") {
this.scrollAnimationId = requestAnimationFrame(scrollAnimation);
} else {
// 回退到 setTimeout 方法
const scrollInterval = 16; // 约 60fps
const scrollStep = 16; // 每次滚动的距离
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The fallback implementation duplicates the scroll step value (16) from the main animation logic. Consider using the same constants defined earlier to avoid code duplication.

Suggested change
const scrollStep = 16; // 每次滚动的距离
const scrollStep = Constants.SIZE_SCROLL_STEP; // 每次滚动的距离

Copilot uses AI. Check for mistakes.

const scrollAnimationFallback = () => {
this.element.scroll({
top: this.element.scrollTop + scrollStep * scrollDirection
});
this.scrollAnimationId = window.setTimeout(scrollAnimationFallback, scrollInterval);
};
this.scrollAnimationId = window.setTimeout(scrollAnimationFallback, scrollInterval);
}
}
} else {
// 离开滚动区域时停止滚动
if (this.scrollAnimationId) {
if (typeof cancelAnimationFrame !== "undefined") {
cancelAnimationFrame(this.scrollAnimationId);
} else {
clearTimeout(this.scrollAnimationId);
}
this.scrollAnimationId = null;
}
}
selectItem = hasClosestByClassName(moveEvent.target as HTMLElement, "b3-list-item") as HTMLElement;
if (!selectItem || selectItem.tagName !== "LI" || selectItem.style.position === "fixed") {
Expand Down Expand Up @@ -416,6 +476,15 @@ export class Outline extends Model {
documentSelf.onselect = null;
ghostElement?.remove();
item.style.opacity = "";
// 清理滚动动画
if (this.scrollAnimationId) {
if (typeof cancelAnimationFrame !== "undefined") {
cancelAnimationFrame(this.scrollAnimationId);
} else {
clearTimeout(this.scrollAnimationId);
}
this.scrollAnimationId = null;
}
Comment on lines +479 to +487
Copy link

Copilot AI Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The scroll animation cleanup logic is duplicated in two places (lines 440-447 and 480-487). Consider extracting this into a private method to reduce code duplication.

Copilot uses AI. Check for mistakes.
if (!selectItem) {
selectItem = this.element.querySelector(".dragover__top, .dragover__bottom, .dragover");
}
Expand Down