|
|
@@ -37,7 +37,6 @@ import { createOpenReviewFile, createSizing } from "@/pages/session/helpers"
|
|
|
import { MessageTimeline } from "@/pages/session/message-timeline"
|
|
|
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
|
|
|
import { resetSessionModel, syncSessionModel } from "@/pages/session/session-model-helpers"
|
|
|
-import { createScrollSpy } from "@/pages/session/scroll-spy"
|
|
|
import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs"
|
|
|
import { SessionSidePanel } from "@/pages/session/session-side-panel"
|
|
|
import { TerminalPanel } from "@/pages/session/terminal-panel"
|
|
|
@@ -486,20 +485,49 @@ export default function Page() {
|
|
|
return "main"
|
|
|
})
|
|
|
|
|
|
- const activeMessage = createMemo(() => {
|
|
|
- if (!store.messageId) return lastUserMessage()
|
|
|
- const found = visibleUserMessages()?.find((m) => m.id === store.messageId)
|
|
|
- return found ?? lastUserMessage()
|
|
|
- })
|
|
|
const setActiveMessage = (message: UserMessage | undefined) => {
|
|
|
+ messageMark = scrollMark
|
|
|
setStore("messageId", message?.id)
|
|
|
}
|
|
|
|
|
|
+ const anchor = (id: string) => `message-${id}`
|
|
|
+
|
|
|
+ const cursor = () => {
|
|
|
+ const root = scroller
|
|
|
+ if (!root) return store.messageId
|
|
|
+
|
|
|
+ const box = root.getBoundingClientRect()
|
|
|
+ const line = box.top + 100
|
|
|
+ const list = [...root.querySelectorAll<HTMLElement>("[data-message-id]")]
|
|
|
+ .map((el) => {
|
|
|
+ const id = el.dataset.messageId
|
|
|
+ if (!id) return
|
|
|
+
|
|
|
+ const rect = el.getBoundingClientRect()
|
|
|
+ return { id, top: rect.top, bottom: rect.bottom }
|
|
|
+ })
|
|
|
+ .filter((item): item is { id: string; top: number; bottom: number } => !!item)
|
|
|
+
|
|
|
+ const shown = list.filter((item) => item.bottom > box.top && item.top < box.bottom)
|
|
|
+ const hit = shown.find((item) => item.top <= line && item.bottom >= line)
|
|
|
+ if (hit) return hit.id
|
|
|
+
|
|
|
+ const near = [...shown].sort((a, b) => {
|
|
|
+ const da = Math.abs(a.top - line)
|
|
|
+ const db = Math.abs(b.top - line)
|
|
|
+ if (da !== db) return da - db
|
|
|
+ return a.top - b.top
|
|
|
+ })[0]
|
|
|
+ if (near) return near.id
|
|
|
+
|
|
|
+ return list.filter((item) => item.top <= line).at(-1)?.id ?? list[0]?.id ?? store.messageId
|
|
|
+ }
|
|
|
+
|
|
|
function navigateMessageByOffset(offset: number) {
|
|
|
const msgs = visibleUserMessages()
|
|
|
if (msgs.length === 0) return
|
|
|
|
|
|
- const current = store.messageId
|
|
|
+ const current = store.messageId && messageMark === scrollMark ? store.messageId : cursor()
|
|
|
const base = current ? msgs.findIndex((m) => m.id === current) : msgs.length
|
|
|
const currentIndex = base === -1 ? msgs.length : base
|
|
|
const targetIndex = currentIndex + offset
|
|
|
@@ -572,6 +600,8 @@ export default function Page() {
|
|
|
let dockHeight = 0
|
|
|
let scroller: HTMLDivElement | undefined
|
|
|
let content: HTMLDivElement | undefined
|
|
|
+ let scrollMark = 0
|
|
|
+ let messageMark = 0
|
|
|
|
|
|
const scrollGestureWindowMs = 250
|
|
|
|
|
|
@@ -616,6 +646,7 @@ export default function Page() {
|
|
|
() => {
|
|
|
setStore("messageId", undefined)
|
|
|
setStore("changes", "session")
|
|
|
+ setUi("pendingMessage", undefined)
|
|
|
},
|
|
|
{ defer: true },
|
|
|
),
|
|
|
@@ -1110,12 +1141,6 @@ export default function Page() {
|
|
|
|
|
|
let scrollStateFrame: number | undefined
|
|
|
let scrollStateTarget: HTMLDivElement | undefined
|
|
|
- const scrollSpy = createScrollSpy({
|
|
|
- onActive: (id) => {
|
|
|
- if (id === store.messageId) return
|
|
|
- setStore("messageId", id)
|
|
|
- },
|
|
|
- })
|
|
|
|
|
|
const updateScrollState = (el: HTMLDivElement) => {
|
|
|
const max = el.scrollHeight - el.clientHeight
|
|
|
@@ -1163,31 +1188,21 @@ export default function Page() {
|
|
|
),
|
|
|
)
|
|
|
|
|
|
- createEffect(
|
|
|
- on(
|
|
|
- sessionKey,
|
|
|
- () => {
|
|
|
- scrollSpy.clear()
|
|
|
- },
|
|
|
- { defer: true },
|
|
|
- ),
|
|
|
- )
|
|
|
-
|
|
|
- const anchor = (id: string) => `message-${id}`
|
|
|
-
|
|
|
const setScrollRef = (el: HTMLDivElement | undefined) => {
|
|
|
scroller = el
|
|
|
autoScroll.scrollRef(el)
|
|
|
- scrollSpy.setContainer(el)
|
|
|
if (el) scheduleScrollState(el)
|
|
|
}
|
|
|
|
|
|
+ const markUserScroll = () => {
|
|
|
+ scrollMark += 1
|
|
|
+ }
|
|
|
+
|
|
|
createResizeObserver(
|
|
|
() => content,
|
|
|
() => {
|
|
|
const el = scroller
|
|
|
if (el) scheduleScrollState(el)
|
|
|
- scrollSpy.markDirty()
|
|
|
},
|
|
|
)
|
|
|
|
|
|
@@ -1220,7 +1235,6 @@ export default function Page() {
|
|
|
if (stick) autoScroll.forceScrollToBottom()
|
|
|
|
|
|
if (el) scheduleScrollState(el)
|
|
|
- scrollSpy.markDirty()
|
|
|
},
|
|
|
)
|
|
|
|
|
|
@@ -1248,7 +1262,6 @@ export default function Page() {
|
|
|
|
|
|
onCleanup(() => {
|
|
|
document.removeEventListener("keydown", handleKeyDown)
|
|
|
- scrollSpy.destroy()
|
|
|
if (reviewFrame !== undefined) cancelAnimationFrame(reviewFrame)
|
|
|
if (scrollStateFrame !== undefined) cancelAnimationFrame(scrollStateFrame)
|
|
|
})
|
|
|
@@ -1280,7 +1293,7 @@ export default function Page() {
|
|
|
<div class="flex-1 min-h-0 overflow-hidden">
|
|
|
<Switch>
|
|
|
<Match when={params.id}>
|
|
|
- <Show when={activeMessage()}>
|
|
|
+ <Show when={lastUserMessage()}>
|
|
|
<MessageTimeline
|
|
|
mobileChanges={mobileChanges()}
|
|
|
mobileFallback={reviewContent({
|
|
|
@@ -1300,8 +1313,7 @@ export default function Page() {
|
|
|
onAutoScrollHandleScroll={autoScroll.handleScroll}
|
|
|
onMarkScrollGesture={markScrollGesture}
|
|
|
hasScrollGesture={hasScrollGesture}
|
|
|
- isDesktop={isDesktop()}
|
|
|
- onScrollSpyScroll={scrollSpy.onScroll}
|
|
|
+ onUserScroll={markUserScroll}
|
|
|
onTurnBackfillScroll={historyWindow.onScrollerScroll}
|
|
|
onAutoScrollInteraction={autoScroll.handleInteraction}
|
|
|
centered={centered()}
|
|
|
@@ -1320,8 +1332,6 @@ export default function Page() {
|
|
|
}}
|
|
|
renderedUserMessages={historyWindow.renderedUserMessages()}
|
|
|
anchor={anchor}
|
|
|
- onRegisterMessage={scrollSpy.register}
|
|
|
- onUnregisterMessage={scrollSpy.unregister}
|
|
|
/>
|
|
|
</Show>
|
|
|
</Match>
|