Files
remanso/src/hooks/useRouteQueryStackedNotes.hook.ts
Julien Calixte 84803c45dd refactor(scroll): clean up debug overlay and pass anchor by param
Removes the temporary on-screen scroll diagnosis panel and the global
window.__scrollAtClick stash. The anchor scrollTop is now captured
synchronously at addStackedNote entry and threaded through
scrollToFocusedNote and scrollToNoteElement to scrollToElement, so no
state survives across calls — nothing to reset on repo or page change.
2026-05-04 23:02:12 +02:00

158 lines
3.6 KiB
TypeScript

import { useWindowSize } from "@vueuse/core"
import { useRouteQuery } from "@vueuse/router"
import { nextTick, readonly } from "vue"
import { getBookmarkWidthPx } from "@/constants/bookmark-width"
import { getNoteWidth } from "@/constants/note-width"
import { useOverlay } from "@/hooks/useOverlay.hook"
export const useRouteQueryStackedNotes = () => {
const stackedNotes = useRouteQuery("stackedNotes", undefined, {
transform: (value: string | string[] | undefined) => {
if (!value) {
return []
}
return Array.isArray(value) ? value : [value]
}
})
const { height } = useWindowSize()
const { scrollToNote, scrollToElement, isMobile } = useOverlay(false)
const scrollToHashInNote = (
cleanSha: string,
hash: string,
smooth: boolean,
attempts = 30
) => {
if (attempts <= 0) {
return
}
const heading = document.querySelector(
`.note-${cleanSha} #${CSS.escape(hash)}`
)
if (heading) {
heading.scrollIntoView({
block: "start",
inline: "nearest",
behavior: smooth ? "smooth" : "auto"
})
return
}
requestAnimationFrame(() => {
scrollToHashInNote(cleanSha, hash, smooth, attempts - 1)
})
}
const scrollToNoteElement = (
cleanNoteId: string,
index: number,
anchorTop?: number,
attempts = 30
) => {
const element = document.querySelector(
`.note-${cleanNoteId}`
) as HTMLElement | null
if (element) {
scrollToElement(element, anchorTop)
return
}
if (attempts <= 0) {
scrollToNote((index + 1) * height.value)
return
}
requestAnimationFrame(() => {
scrollToNoteElement(cleanNoteId, index, anchorTop, attempts - 1)
})
}
type ScrollToFocusedNoteOptions = {
noteId?: string | null
notes?: string[]
hash?: string
smoothHash?: boolean
anchorTop?: number
}
const scrollToFocusedNote = ({
noteId = null,
notes = stackedNotes.value,
hash,
smoothHash = false,
anchorTop
}: ScrollToFocusedNoteOptions = {}) => {
nextTick(() => {
const index = noteId ? notes.findIndex((nid) => nid === noteId) : 0
if (isMobile.value) {
if (noteId) {
scrollToNoteElement(noteId.replaceAll(":", "-"), index, anchorTop)
} else {
scrollToNote(0)
}
} else {
if (noteId) {
const left = (index + 1) * (getNoteWidth() - getBookmarkWidthPx())
scrollToNote(left)
} else {
scrollToNote(0)
}
}
if (hash && noteId) {
scrollToHashInNote(noteId.replaceAll(":", "-"), hash, smoothHash)
}
})
}
const addStackedNote = (
currentSha: string,
sha: string,
selector?: string,
hash?: string
) => {
const anchorTop =
document.getElementById("main-app")?.scrollTop ?? undefined
if (stackedNotes.value.includes(sha)) {
scrollToFocusedNote({
noteId: selector ?? sha,
hash,
smoothHash: true,
anchorTop
})
return
}
if (!currentSha) {
stackedNotes.value = [sha]
} else {
const [splittedStackedNotes] = stackedNotes.value
.join(";")
.split(currentSha)
const newStackedNotes = [
...splittedStackedNotes.replaceAll(";;", ";").split(";"),
currentSha,
sha
].filter((sha) => !!sha)
stackedNotes.value = newStackedNotes
}
scrollToFocusedNote({ noteId: selector ?? sha, hash, anchorTop })
}
return {
stackedNotes: readonly(stackedNotes),
addStackedNote,
scrollToFocusedNote
}
}