docs: fix share page scroll performance

This commit is contained in:
Jay V 2025-07-03 19:15:38 -04:00
parent e7fcb692a4
commit b8de69dced

View file

@ -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>&ldquo;{splitArgs().pattern}&rdquo;</b>
<b>
&ldquo;{splitArgs().pattern}&rdquo;
</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)}