app: prefer using useLocation instead of window.location (#15989)
parent
f363904feb
commit
7948de1612
1
bun.lock
1
bun.lock
|
|
@ -484,6 +484,7 @@
|
||||||
"@solid-primitives/media": "2.3.3",
|
"@solid-primitives/media": "2.3.3",
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
|
"@solidjs/router": "catalog:",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
import { A, useNavigate, useParams } from "@solidjs/router"
|
import type { Message, Session, TextPart, UserMessage } from "@opencode-ai/sdk/v2/client"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { useLayout, type LocalProject, getAvatarColors } from "@/context/layout"
|
|
||||||
import { useNotification } from "@/context/notification"
|
|
||||||
import { usePermission } from "@/context/permission"
|
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
|
||||||
import { Avatar } from "@opencode-ai/ui/avatar"
|
import { Avatar } from "@opencode-ai/ui/avatar"
|
||||||
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
import { HoverCard } from "@opencode-ai/ui/hover-card"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
|
|
@ -12,12 +6,18 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
import { MessageNav } from "@opencode-ai/ui/message-nav"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { type Message, type Session, type TextPart, type UserMessage } from "@opencode-ai/sdk/v2/client"
|
import { A, useNavigate, useParams } from "@solidjs/router"
|
||||||
import { For, Match, Show, Switch, createMemo, onCleanup, type Accessor, type JSX } from "solid-js"
|
import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
|
||||||
|
import { useNotification } from "@/context/notification"
|
||||||
|
import { usePermission } from "@/context/permission"
|
||||||
import { agentColor } from "@/utils/agent"
|
import { agentColor } from "@/utils/agent"
|
||||||
import { hasProjectPermissions } from "./helpers"
|
|
||||||
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
|
||||||
|
import { hasProjectPermissions } from "./helpers"
|
||||||
|
|
||||||
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
|
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
|
||||||
|
|
||||||
|
|
@ -231,7 +231,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
||||||
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
|
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
|
||||||
const isActive = createMemo(() => props.session.id === params.id)
|
const isActive = createMemo(() => props.session.id === params.id)
|
||||||
|
|
||||||
const hoverPrefetch = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
const hoverPrefetch = {
|
||||||
|
current: undefined as ReturnType<typeof setTimeout> | undefined,
|
||||||
|
}
|
||||||
const cancelHoverPrefetch = () => {
|
const cancelHoverPrefetch = () => {
|
||||||
if (hoverPrefetch.current === undefined) return
|
if (hoverPrefetch.current === undefined) return
|
||||||
clearTimeout(hoverPrefetch.current)
|
clearTimeout(hoverPrefetch.current)
|
||||||
|
|
@ -300,17 +302,15 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
|
||||||
setHoverSession={props.setHoverSession}
|
setHoverSession={props.setHoverSession}
|
||||||
messageLabel={messageLabel}
|
messageLabel={messageLabel}
|
||||||
onMessageSelect={(message) => {
|
onMessageSelect={(message) => {
|
||||||
if (!isActive()) {
|
if (!isActive())
|
||||||
layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id)
|
layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id)
|
||||||
navigate(`${props.slug}/session/${props.session.id}`)
|
|
||||||
return
|
navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`)
|
||||||
}
|
|
||||||
window.history.replaceState(null, "", `#message-${message.id}`)
|
|
||||||
window.dispatchEvent(new HashChangeEvent("hashchange"))
|
|
||||||
}}
|
}}
|
||||||
trigger={item}
|
trigger={item}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
|
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
|
||||||
classList={{
|
classList={{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const messageIdFromHash = (hash: string) => {
|
||||||
|
const value = hash.startsWith("#") ? hash.slice(1) : hash
|
||||||
|
const match = value.match(/^message-(.+)$/)
|
||||||
|
if (!match) return
|
||||||
|
return match[1]
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { messageIdFromHash } from "./use-session-hash-scroll"
|
import { messageIdFromHash } from "./message-id-from-hash"
|
||||||
|
|
||||||
describe("messageIdFromHash", () => {
|
describe("messageIdFromHash", () => {
|
||||||
test("parses hash with leading #", () => {
|
test("parses hash with leading #", () => {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
|
import type { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
import { useLocation, useNavigate } from "@solidjs/router"
|
||||||
|
import { createEffect, createMemo, onMount } from "solid-js"
|
||||||
|
import { messageIdFromHash } from "./message-id-from-hash"
|
||||||
|
|
||||||
export const messageIdFromHash = (hash: string) => {
|
export { messageIdFromHash } from "./message-id-from-hash"
|
||||||
const value = hash.startsWith("#") ? hash.slice(1) : hash
|
|
||||||
const match = value.match(/^message-(.+)$/)
|
|
||||||
if (!match) return
|
|
||||||
return match[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSessionHashScroll = (input: {
|
export const useSessionHashScroll = (input: {
|
||||||
sessionKey: () => string
|
sessionKey: () => string
|
||||||
|
|
@ -30,13 +27,18 @@ export const useSessionHashScroll = (input: {
|
||||||
const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i])))
|
const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i])))
|
||||||
let pendingKey = ""
|
let pendingKey = ""
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const clearMessageHash = () => {
|
const clearMessageHash = () => {
|
||||||
if (!window.location.hash) return
|
if (!location.hash) return
|
||||||
window.history.replaceState(null, "", window.location.href.replace(/#.*$/, ""))
|
navigate(location.pathname + location.search, { replace: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateHash = (id: string) => {
|
const updateHash = (id: string) => {
|
||||||
window.history.replaceState(null, "", `#${input.anchor(id)}`)
|
navigate(location.pathname + location.search + `#${input.anchor(id)}`, {
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
|
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
|
||||||
|
|
@ -53,6 +55,7 @@ export const useSessionHashScroll = (input: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
|
||||||
|
console.log({ message, behavior })
|
||||||
if (input.currentMessageId() !== message.id) input.setActiveMessage(message)
|
if (input.currentMessageId() !== message.id) input.setActiveMessage(message)
|
||||||
|
|
||||||
const index = messageIndex().get(message.id) ?? -1
|
const index = messageIndex().get(message.id) ?? -1
|
||||||
|
|
@ -100,7 +103,7 @@ export const useSessionHashScroll = (input: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyHash = (behavior: ScrollBehavior) => {
|
const applyHash = (behavior: ScrollBehavior) => {
|
||||||
const hash = window.location.hash.slice(1)
|
const hash = location.hash.slice(1)
|
||||||
if (!hash) {
|
if (!hash) {
|
||||||
input.autoScroll.forceScrollToBottom()
|
input.autoScroll.forceScrollToBottom()
|
||||||
const el = input.scroller()
|
const el = input.scroller()
|
||||||
|
|
@ -132,6 +135,7 @@ export const useSessionHashScroll = (input: {
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
location.hash
|
||||||
if (!input.sessionID() || !input.messagesReady()) return
|
if (!input.sessionID() || !input.messagesReady()) return
|
||||||
requestAnimationFrame(() => applyHash("auto"))
|
requestAnimationFrame(() => applyHash("auto"))
|
||||||
})
|
})
|
||||||
|
|
@ -155,7 +159,7 @@ export const useSessionHashScroll = (input: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetId) targetId = messageIdFromHash(window.location.hash)
|
if (!targetId) targetId = messageIdFromHash(location.hash)
|
||||||
if (!targetId) return
|
if (!targetId) return
|
||||||
if (input.currentMessageId() === targetId) return
|
if (input.currentMessageId() === targetId) return
|
||||||
|
|
||||||
|
|
@ -171,14 +175,6 @@ export const useSessionHashScroll = (input: {
|
||||||
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
|
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
|
||||||
window.history.scrollRestoration = "manual"
|
window.history.scrollRestoration = "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = () => {
|
|
||||||
if (!input.sessionID() || !input.messagesReady()) return
|
|
||||||
requestAnimationFrame(() => applyHash("auto"))
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("hashchange", handler)
|
|
||||||
onCleanup(() => window.removeEventListener("hashchange", handler))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export const setNavigate = (fn: (href: string) => void) => {
|
||||||
export const handleNotificationClick = (href?: string) => {
|
export const handleNotificationClick = (href?: string) => {
|
||||||
window.focus()
|
window.focus()
|
||||||
if (!href) return
|
if (!href) return
|
||||||
if (nav) nav(href)
|
if (nav) return nav(href)
|
||||||
else window.location.assign(href)
|
console.warn("notification-click: navigate function not set, falling back to window.location.assign")
|
||||||
|
window.location.assign(href)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
"@solid-primitives/media": "2.3.3",
|
"@solid-primitives/media": "2.3.3",
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
|
"@solidjs/router": "catalog:",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ import { TextShimmer } from "./text-shimmer"
|
||||||
import { AnimatedCountList } from "./tool-count-summary"
|
import { AnimatedCountList } from "./tool-count-summary"
|
||||||
import { ToolStatusTitle } from "./tool-status-title"
|
import { ToolStatusTitle } from "./tool-status-title"
|
||||||
import { animate } from "motion"
|
import { animate } from "motion"
|
||||||
|
import { useLocation } from "@solidjs/router"
|
||||||
|
|
||||||
function ShellSubmessage(props: { text: string; animate?: boolean }) {
|
function ShellSubmessage(props: { text: string; animate?: boolean }) {
|
||||||
let widthRef: HTMLSpanElement | undefined
|
let widthRef: HTMLSpanElement | undefined
|
||||||
|
|
@ -1471,6 +1472,7 @@ ToolRegistry.register({
|
||||||
render(props) {
|
render(props) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
const location = useLocation()
|
||||||
const childSessionId = () => props.metadata.sessionId as string | undefined
|
const childSessionId = () => props.metadata.sessionId as string | undefined
|
||||||
const title = createMemo(() => i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }))
|
const title = createMemo(() => i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }))
|
||||||
const description = createMemo(() => {
|
const description = createMemo(() => {
|
||||||
|
|
@ -1487,8 +1489,7 @@ ToolRegistry.register({
|
||||||
const direct = data.sessionHref?.(sessionId)
|
const direct = data.sessionHref?.(sessionId)
|
||||||
if (direct) return direct
|
if (direct) return direct
|
||||||
|
|
||||||
if (typeof window === "undefined") return
|
const path = location.pathname
|
||||||
const path = window.location.pathname
|
|
||||||
const idx = path.indexOf("/session")
|
const idx = path.indexOf("/session")
|
||||||
if (idx === -1) return
|
if (idx === -1) return
|
||||||
return `${path.slice(0, idx)}/session/${sessionId}`
|
return `${path.slice(0, idx)}/session/${sessionId}`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue