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