Files
remanso/src/hooks/useRouteQueryStackedNotes.hook.ts
Julien Calixte c00065ce4a refactor(navigation): scrollToFocusedNote takes an options object
Smooth-scroll for the anchor jump when the target note is already
stacked, instant otherwise. While threading the new flag, the four
positional params got hard to read, so collapse them into
{ noteId, notes, hash, smoothHash } and update all call sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 09:56:32 +02:00

133 lines
3.1 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, 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)
})
}
type ScrollToFocusedNoteOptions = {
noteId?: string | null
notes?: string[]
hash?: string
smoothHash?: boolean
}
const scrollToFocusedNote = ({
noteId = null,
notes = stackedNotes.value,
hash,
smoothHash = false
}: ScrollToFocusedNoteOptions = {}) => {
nextTick(() => {
const index = noteId ? notes.findIndex((nid) => nid === noteId) : 0
if (isMobile.value) {
if (noteId) {
const cleanNoteId = noteId.replaceAll(":", "-")
const element = document.querySelector(
`.note-${cleanNoteId}`
) as HTMLElement
const top = (index + 1) * (element?.clientHeight ?? height.value)
scrollToNote(top)
} 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
) => {
if (stackedNotes.value.includes(sha)) {
scrollToFocusedNote({
noteId: selector ?? sha,
hash,
smoothHash: true
})
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 })
}
return {
stackedNotes: readonly(stackedNotes),
addStackedNote,
scrollToFocusedNote
}
}