mirror of
https://github.com/sst/opencode.git
synced 2025-07-07 16:14:59 +00:00
docs: fix share page scroll performance
This commit is contained in:
parent
e7fcb692a4
commit
b8de69dced
1 changed files with 119 additions and 35 deletions
|
@ -597,6 +597,8 @@ export default function Share(props: {
|
|||
}) {
|
||||
let lastScrollY = 0
|
||||
let scrollTimeout: number | undefined
|
||||
let scrollSentinel: HTMLElement | undefined
|
||||
let scrollObserver: IntersectionObserver | undefined
|
||||
|
||||
const id = props.id
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
|
@ -604,6 +606,7 @@ export default function Share(props: {
|
|||
|
||||
const [showScrollButton, setShowScrollButton] = createSignal(false)
|
||||
const [isButtonHovered, setIsButtonHovered] = createSignal(false)
|
||||
const [isNearBottom, setIsNearBottom] = createSignal(false)
|
||||
|
||||
const [store, setStore] = createStore<{
|
||||
info?: Session.Info
|
||||
|
@ -713,10 +716,9 @@ export default function Share(props: {
|
|||
const currentScrollY = window.scrollY
|
||||
const isScrollingDown = currentScrollY > lastScrollY
|
||||
const scrolled = currentScrollY > 200 // Show after scrolling 200px
|
||||
const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100
|
||||
|
||||
// Only show when scrolling down, scrolled enough, and not near bottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom()
|
||||
|
||||
// Update last scroll position
|
||||
lastScrollY = currentScrollY
|
||||
|
@ -732,7 +734,7 @@ export default function Share(props: {
|
|||
if (!isButtonHovered()) {
|
||||
setShowScrollButton(false)
|
||||
}
|
||||
}, 3000)
|
||||
}, 1500)
|
||||
} else if (!isButtonHovered()) {
|
||||
// Only hide if not hovered (to prevent disappearing while user is about to click)
|
||||
setShowScrollButton(false)
|
||||
|
@ -744,6 +746,26 @@ export default function Share(props: {
|
|||
|
||||
onMount(() => {
|
||||
lastScrollY = window.scrollY // Initialize scroll position
|
||||
|
||||
// Create sentinel element
|
||||
const sentinel = document.createElement("div")
|
||||
sentinel.style.height = "1px"
|
||||
sentinel.style.position = "absolute"
|
||||
sentinel.style.bottom = "100px"
|
||||
sentinel.style.width = "100%"
|
||||
sentinel.style.pointerEvents = "none"
|
||||
document.body.appendChild(sentinel)
|
||||
|
||||
// Create intersection observer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
setIsNearBottom(entries[0].isIntersecting)
|
||||
})
|
||||
observer.observe(sentinel)
|
||||
|
||||
// Store references for cleanup
|
||||
scrollSentinel = sentinel
|
||||
scrollObserver = observer
|
||||
|
||||
checkScrollNeed()
|
||||
window.addEventListener("scroll", checkScrollNeed)
|
||||
window.addEventListener("resize", checkScrollNeed)
|
||||
|
@ -752,6 +774,15 @@ export default function Share(props: {
|
|||
onCleanup(() => {
|
||||
window.removeEventListener("scroll", checkScrollNeed)
|
||||
window.removeEventListener("resize", checkScrollNeed)
|
||||
|
||||
// Clean up observer and sentinel
|
||||
if (scrollObserver) {
|
||||
scrollObserver.disconnect()
|
||||
}
|
||||
if (scrollSentinel) {
|
||||
document.body.removeChild(scrollSentinel)
|
||||
}
|
||||
|
||||
if (scrollTimeout) {
|
||||
clearTimeout(scrollTimeout)
|
||||
}
|
||||
|
@ -855,7 +886,6 @@ export default function Share(props: {
|
|||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -880,8 +910,11 @@ export default function Share(props: {
|
|||
)
|
||||
return null
|
||||
|
||||
const anchor = createMemo(() => `${msg.id}-${partIndex()}`)
|
||||
const [showResults, setShowResults] = createSignal(false)
|
||||
const anchor = createMemo(
|
||||
() => `${msg.id}-${partIndex()}`,
|
||||
)
|
||||
const [showResults, setShowResults] =
|
||||
createSignal(false)
|
||||
const isLastPart = createMemo(
|
||||
() =>
|
||||
data().messages.length === msgIndex() + 1 &&
|
||||
|
@ -903,7 +936,9 @@ export default function Share(props: {
|
|||
const duration = DateTime.fromMillis(
|
||||
metadata?.time.end || 0,
|
||||
)
|
||||
.diff(DateTime.fromMillis(metadata?.time.start || 0))
|
||||
.diff(
|
||||
DateTime.fromMillis(metadata?.time.start || 0),
|
||||
)
|
||||
.toMillis()
|
||||
|
||||
return { metadata, args, result, duration }
|
||||
|
@ -921,7 +956,9 @@ export default function Share(props: {
|
|||
{/* User text */}
|
||||
<Match
|
||||
when={
|
||||
msg.role === "user" && part.type === "text" && part
|
||||
msg.role === "user" &&
|
||||
part.type === "text" &&
|
||||
part
|
||||
}
|
||||
>
|
||||
{(part) => (
|
||||
|
@ -972,7 +1009,9 @@ export default function Share(props: {
|
|||
expand={isLastPart()}
|
||||
text={stripEnclosingTag(part().text)}
|
||||
/>
|
||||
<Show when={isLastPart() && data().completed}>
|
||||
<Show
|
||||
when={isLastPart() && data().completed}
|
||||
>
|
||||
<span
|
||||
data-part-footer
|
||||
title={DateTime.fromMillis(
|
||||
|
@ -1041,7 +1080,8 @@ export default function Share(props: {
|
|||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const matches = () => toolData()?.metadata?.matches
|
||||
const matches = () =>
|
||||
toolData()?.metadata?.matches
|
||||
const splitArgs = () => {
|
||||
const { pattern, ...rest } = toolData()?.args
|
||||
return { pattern, rest }
|
||||
|
@ -1066,11 +1106,14 @@ export default function Share(props: {
|
|||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>Grep</span>
|
||||
<b>“{splitArgs().pattern}”</b>
|
||||
<b>
|
||||
“{splitArgs().pattern}”
|
||||
</b>
|
||||
</div>
|
||||
<Show
|
||||
when={
|
||||
Object.keys(splitArgs().rest).length > 0
|
||||
Object.keys(splitArgs().rest)
|
||||
.length > 0
|
||||
}
|
||||
>
|
||||
<div data-part-tool-args>
|
||||
|
@ -1299,8 +1342,10 @@ export default function Share(props: {
|
|||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const preview = () => toolData()?.metadata?.preview
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const preview = () =>
|
||||
toolData()?.metadata?.preview
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -1333,7 +1378,9 @@ export default function Share(props: {
|
|||
</div>
|
||||
</Match>
|
||||
{/* Always try to show CodeBlock if preview is available (even if empty string) */}
|
||||
<Match when={typeof preview() === 'string'}>
|
||||
<Match
|
||||
when={typeof preview() === "string"}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
showCopy="Show preview"
|
||||
|
@ -1346,7 +1393,9 @@ export default function Share(props: {
|
|||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={preview()}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1354,7 +1403,12 @@ export default function Share(props: {
|
|||
</div>
|
||||
</Match>
|
||||
{/* Fallback to TextPart if preview is not a string (e.g. undefined) AND result exists */}
|
||||
<Match when={typeof preview() !== 'string' && toolData()?.result}>
|
||||
<Match
|
||||
when={
|
||||
typeof preview() !== "string" &&
|
||||
toolData()?.result
|
||||
}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
results={showResults()}
|
||||
|
@ -1398,7 +1452,8 @@ export default function Share(props: {
|
|||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const content = () => toolData()?.args?.content
|
||||
const diagnostics = createMemo(() =>
|
||||
getDiagnostics(
|
||||
|
@ -1415,7 +1470,10 @@ export default function Share(props: {
|
|||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconDocumentPlus width={18} height={18} />
|
||||
<IconDocumentPlus
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
|
@ -1435,7 +1493,7 @@ export default function Share(props: {
|
|||
<div data-part-tool-result>
|
||||
<ErrorPart>
|
||||
{formatErrorString(
|
||||
toolData()?.result
|
||||
toolData()?.result,
|
||||
)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
|
@ -1453,8 +1511,12 @@ export default function Share(props: {
|
|||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
code={toolData()?.args?.content}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={
|
||||
toolData()?.args?.content
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
|
@ -1481,8 +1543,10 @@ export default function Share(props: {
|
|||
>
|
||||
{(_part) => {
|
||||
const diff = () => toolData()?.metadata?.diff
|
||||
const message = () => toolData()?.metadata?.message
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const message = () =>
|
||||
toolData()?.metadata?.message
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const filePath = createMemo(() =>
|
||||
stripWorkingDirectory(
|
||||
toolData()?.args.filePath,
|
||||
|
@ -1504,7 +1568,10 @@ export default function Share(props: {
|
|||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconPencilSquare width={18} height={18} />
|
||||
<IconPencilSquare
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
|
@ -1527,7 +1594,9 @@ export default function Share(props: {
|
|||
<Match when={diff()}>
|
||||
<div data-part-tool-edit>
|
||||
<DiffView
|
||||
class={styles["diff-code-block"]}
|
||||
class={
|
||||
styles["diff-code-block"]
|
||||
}
|
||||
diff={diff()}
|
||||
lang={getShikiLang(filePath())}
|
||||
/>
|
||||
|
@ -1556,9 +1625,12 @@ export default function Share(props: {
|
|||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const command = () => toolData()?.metadata?.title
|
||||
const desc = () => toolData()?.metadata?.description
|
||||
const result = () => toolData()?.metadata?.stdout
|
||||
const command = () =>
|
||||
toolData()?.metadata?.title
|
||||
const desc = () =>
|
||||
toolData()?.metadata?.description
|
||||
const result = () =>
|
||||
toolData()?.metadata?.stdout
|
||||
const error = () => toolData()?.metadata?.stderr
|
||||
|
||||
return (
|
||||
|
@ -1569,7 +1641,10 @@ export default function Share(props: {
|
|||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconCommandLine width={18} height={18} />
|
||||
<IconCommandLine
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
|
@ -1604,7 +1679,9 @@ export default function Share(props: {
|
|||
>
|
||||
{(_part) => {
|
||||
const todos = createMemo(() =>
|
||||
sortTodosByStatus(toolData()?.args?.todos ?? []),
|
||||
sortTodosByStatus(
|
||||
toolData()?.args?.todos ?? [],
|
||||
),
|
||||
)
|
||||
const starting = () =>
|
||||
todos().every((t) => t.status === "pending")
|
||||
|
@ -1670,7 +1747,8 @@ export default function Share(props: {
|
|||
{(_part) => {
|
||||
const url = () => toolData()?.args.url
|
||||
const format = () => toolData()?.args.format
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -1793,7 +1871,8 @@ export default function Share(props: {
|
|||
</Match>
|
||||
<Match
|
||||
when={
|
||||
part().toolInvocation.state === "call"
|
||||
part().toolInvocation.state ===
|
||||
"call"
|
||||
}
|
||||
>
|
||||
<TextPart
|
||||
|
@ -1839,7 +1918,10 @@ export default function Share(props: {
|
|||
</Match>
|
||||
|
||||
<Match when={msg.role === "user"}>
|
||||
<IconUserCircle width={18} height={18} />
|
||||
<IconUserCircle
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</AnchorIcon>
|
||||
|
@ -1848,7 +1930,9 @@ export default function Share(props: {
|
|||
<div data-section="content">
|
||||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>{part.type}</span>
|
||||
<span data-element-label>
|
||||
{part.type}
|
||||
</span>
|
||||
</div>
|
||||
<TextPart
|
||||
text={JSON.stringify(part, null, 2)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue