|
|
@@ -39,6 +39,7 @@ import {
|
|
|
IconMagnifyingGlass,
|
|
|
IconWrenchScrewdriver,
|
|
|
IconDocumentMagnifyingGlass,
|
|
|
+ IconArrowDown,
|
|
|
} from "./icons"
|
|
|
import DiffView from "./DiffView"
|
|
|
import CodeBlock from "./CodeBlock"
|
|
|
@@ -594,12 +595,17 @@ export default function Share(props: {
|
|
|
info: Session.Info
|
|
|
messages: Record<string, Message.Info>
|
|
|
}) {
|
|
|
+ let lastScrollY = 0
|
|
|
let hasScrolled = false
|
|
|
+ let scrollTimeout: number | undefined
|
|
|
|
|
|
const id = props.id
|
|
|
const params = new URLSearchParams(window.location.search)
|
|
|
const debug = params.get("debug") === "true"
|
|
|
|
|
|
+ const [showScrollButton, setShowScrollButton] = createSignal(false)
|
|
|
+ const [isButtonHovered, setIsButtonHovered] = createSignal(false)
|
|
|
+
|
|
|
const anchorId = createMemo<string | null>(() => {
|
|
|
const raw = window.location.hash.slice(1)
|
|
|
const [id] = raw.split("-")
|
|
|
@@ -715,6 +721,54 @@ export default function Share(props: {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ function checkScrollNeed() {
|
|
|
+ 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
|
|
|
+
|
|
|
+ // Update last scroll position
|
|
|
+ lastScrollY = currentScrollY
|
|
|
+
|
|
|
+ if (shouldShow) {
|
|
|
+ setShowScrollButton(true)
|
|
|
+ // Clear existing timeout
|
|
|
+ if (scrollTimeout) {
|
|
|
+ clearTimeout(scrollTimeout)
|
|
|
+ }
|
|
|
+ // Hide button after 3 seconds of no scrolling (unless hovered)
|
|
|
+ scrollTimeout = window.setTimeout(() => {
|
|
|
+ if (!isButtonHovered()) {
|
|
|
+ setShowScrollButton(false)
|
|
|
+ }
|
|
|
+ }, 3000)
|
|
|
+ } else if (!isButtonHovered()) {
|
|
|
+ // Only hide if not hovered (to prevent disappearing while user is about to click)
|
|
|
+ setShowScrollButton(false)
|
|
|
+ if (scrollTimeout) {
|
|
|
+ clearTimeout(scrollTimeout)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onMount(() => {
|
|
|
+ lastScrollY = window.scrollY // Initialize scroll position
|
|
|
+ checkScrollNeed()
|
|
|
+ window.addEventListener("scroll", checkScrollNeed)
|
|
|
+ window.addEventListener("resize", checkScrollNeed)
|
|
|
+ })
|
|
|
+
|
|
|
+ onCleanup(() => {
|
|
|
+ window.removeEventListener("scroll", checkScrollNeed)
|
|
|
+ window.removeEventListener("resize", checkScrollNeed)
|
|
|
+ if (scrollTimeout) {
|
|
|
+ clearTimeout(scrollTimeout)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
const data = createMemo(() => {
|
|
|
const result = {
|
|
|
rootDir: undefined as string | undefined,
|
|
|
@@ -825,6 +879,7 @@ export default function Share(props: {
|
|
|
</span>
|
|
|
)}
|
|
|
</div>
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -1902,6 +1957,36 @@ export default function Share(props: {
|
|
|
</div>
|
|
|
</div>
|
|
|
</Show>
|
|
|
+
|
|
|
+ <Show when={showScrollButton()}>
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class={styles["scroll-button"]}
|
|
|
+ onClick={() =>
|
|
|
+ document.body.scrollIntoView({ behavior: "smooth", block: "end" })
|
|
|
+ }
|
|
|
+ onMouseEnter={() => {
|
|
|
+ setIsButtonHovered(true)
|
|
|
+ if (scrollTimeout) {
|
|
|
+ clearTimeout(scrollTimeout)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onMouseLeave={() => {
|
|
|
+ setIsButtonHovered(false)
|
|
|
+ if (showScrollButton()) {
|
|
|
+ scrollTimeout = window.setTimeout(() => {
|
|
|
+ if (!isButtonHovered()) {
|
|
|
+ setShowScrollButton(false)
|
|
|
+ }
|
|
|
+ }, 3000)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ title="Scroll to bottom"
|
|
|
+ aria-label="Scroll to bottom"
|
|
|
+ >
|
|
|
+ <IconArrowDown width={20} height={20} />
|
|
|
+ </button>
|
|
|
+ </Show>
|
|
|
</main>
|
|
|
)
|
|
|
}
|