mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-26 10:34:47 +00:00
chore: refactoring and tests, splitting up files (#12495)
This commit is contained in:
174
packages/app/src/pages/session/use-session-hash-scroll.ts
Normal file
174
packages/app/src/pages/session/use-session-hash-scroll.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { createEffect, on, onCleanup } from "solid-js"
|
||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||
|
||||
export const messageIdFromHash = (hash: string) => {
|
||||
const value = hash.startsWith("#") ? hash.slice(1) : hash
|
||||
const match = value.match(/^message-(.+)$/)
|
||||
if (!match) return
|
||||
return match[1]
|
||||
}
|
||||
|
||||
export const useSessionHashScroll = (input: {
|
||||
sessionKey: () => string
|
||||
sessionID: () => string | undefined
|
||||
messagesReady: () => boolean
|
||||
visibleUserMessages: () => UserMessage[]
|
||||
turnStart: () => number
|
||||
currentMessageId: () => string | undefined
|
||||
pendingMessage: () => string | undefined
|
||||
setPendingMessage: (value: string | undefined) => void
|
||||
setActiveMessage: (message: UserMessage | undefined) => void
|
||||
setTurnStart: (value: number) => void
|
||||
scheduleTurnBackfill: () => void
|
||||
autoScroll: { pause: () => void; forceScrollToBottom: () => void }
|
||||
scroller: () => HTMLDivElement | undefined
|
||||
anchor: (id: string) => string
|
||||
scheduleScrollState: (el: HTMLDivElement) => void
|
||||
consumePendingMessage: (key: string) => string | undefined
|
||||
}) => {
|
||||
const clearMessageHash = () => {
|
||||
if (!window.location.hash) return
|
||||
window.history.replaceState(null, "", window.location.href.replace(/#.*$/, ""))
|
||||
}
|
||||
|
||||
const updateHash = (id: string) => {
|
||||
window.history.replaceState(null, "", `#${input.anchor(id)}`)
|
||||
}
|
||||
|
||||
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
|
||||
const root = input.scroller()
|
||||
if (!root) return false
|
||||
|
||||
const a = el.getBoundingClientRect()
|
||||
const b = root.getBoundingClientRect()
|
||||
const top = a.top - b.top + root.scrollTop
|
||||
root.scrollTo({ top, behavior })
|
||||
return true
|
||||
}
|
||||
|
||||
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
||||
input.setActiveMessage(message)
|
||||
|
||||
const msgs = input.visibleUserMessages()
|
||||
const index = msgs.findIndex((m) => m.id === message.id)
|
||||
if (index !== -1 && index < input.turnStart()) {
|
||||
input.setTurnStart(index)
|
||||
input.scheduleTurnBackfill()
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const el = document.getElementById(input.anchor(message.id))
|
||||
if (!el) {
|
||||
requestAnimationFrame(() => {
|
||||
const next = document.getElementById(input.anchor(message.id))
|
||||
if (!next) return
|
||||
scrollToElement(next, behavior)
|
||||
})
|
||||
return
|
||||
}
|
||||
scrollToElement(el, behavior)
|
||||
})
|
||||
|
||||
updateHash(message.id)
|
||||
return
|
||||
}
|
||||
|
||||
const el = document.getElementById(input.anchor(message.id))
|
||||
if (!el) {
|
||||
updateHash(message.id)
|
||||
requestAnimationFrame(() => {
|
||||
const next = document.getElementById(input.anchor(message.id))
|
||||
if (!next) return
|
||||
if (!scrollToElement(next, behavior)) return
|
||||
})
|
||||
return
|
||||
}
|
||||
if (scrollToElement(el, behavior)) {
|
||||
updateHash(message.id)
|
||||
return
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const next = document.getElementById(input.anchor(message.id))
|
||||
if (!next) return
|
||||
if (!scrollToElement(next, behavior)) return
|
||||
})
|
||||
updateHash(message.id)
|
||||
}
|
||||
|
||||
const applyHash = (behavior: ScrollBehavior) => {
|
||||
const hash = window.location.hash.slice(1)
|
||||
if (!hash) {
|
||||
input.autoScroll.forceScrollToBottom()
|
||||
const el = input.scroller()
|
||||
if (el) input.scheduleScrollState(el)
|
||||
return
|
||||
}
|
||||
|
||||
const messageId = messageIdFromHash(hash)
|
||||
if (messageId) {
|
||||
input.autoScroll.pause()
|
||||
const msg = input.visibleUserMessages().find((m) => m.id === messageId)
|
||||
if (msg) {
|
||||
scrollToMessage(msg, behavior)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const target = document.getElementById(hash)
|
||||
if (target) {
|
||||
input.autoScroll.pause()
|
||||
scrollToElement(target, behavior)
|
||||
return
|
||||
}
|
||||
|
||||
input.autoScroll.forceScrollToBottom()
|
||||
const el = input.scroller()
|
||||
if (el) input.scheduleScrollState(el)
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(input.sessionKey, (key) => {
|
||||
if (!input.sessionID()) return
|
||||
const messageID = input.consumePendingMessage(key)
|
||||
if (!messageID) return
|
||||
input.setPendingMessage(messageID)
|
||||
}),
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (!input.sessionID() || !input.messagesReady()) return
|
||||
requestAnimationFrame(() => applyHash("auto"))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!input.sessionID() || !input.messagesReady()) return
|
||||
|
||||
input.visibleUserMessages().length
|
||||
input.turnStart()
|
||||
|
||||
const targetId = input.pendingMessage() ?? messageIdFromHash(window.location.hash)
|
||||
if (!targetId) return
|
||||
if (input.currentMessageId() === targetId) return
|
||||
|
||||
const msg = input.visibleUserMessages().find((m) => m.id === targetId)
|
||||
if (!msg) return
|
||||
|
||||
if (input.pendingMessage() === targetId) input.setPendingMessage(undefined)
|
||||
input.autoScroll.pause()
|
||||
requestAnimationFrame(() => scrollToMessage(msg, "auto"))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!input.sessionID() || !input.messagesReady()) return
|
||||
const handler = () => requestAnimationFrame(() => applyHash("auto"))
|
||||
window.addEventListener("hashchange", handler)
|
||||
onCleanup(() => window.removeEventListener("hashchange", handler))
|
||||
})
|
||||
|
||||
return {
|
||||
clearMessageHash,
|
||||
scrollToMessage,
|
||||
applyHash,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user