From 60a2e5bda1787e879bab1d1f91ffb970ddb3582e Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Wed, 21 Jan 2026 14:54:10 +0800 Subject: [PATCH] scroll restore --- next-ui/src/pages/libraries/[id]/books.vue | 1 + .../src/pages/libraries/[id]/collections.vue | 1 + .../src/pages/libraries/[id]/readlists.vue | 1 + next-ui/src/pages/libraries/[id]/series.vue | 1 + next-ui/src/plugins/index.ts | 2 ++ next-ui/src/router/index.ts | 13 +++++++++++ next-ui/src/router/login-guard.ts | 8 ++++--- next-ui/src/router/role-guard.ts | 8 ++++--- next-ui/src/router/scroll.ts | 23 +++++++++++++++++++ next-ui/src/types/RouterMeta.d.ts | 4 ++++ 10 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 next-ui/src/router/scroll.ts diff --git a/next-ui/src/pages/libraries/[id]/books.vue b/next-ui/src/pages/libraries/[id]/books.vue index b3be651f..05191054 100644 --- a/next-ui/src/pages/libraries/[id]/books.vue +++ b/next-ui/src/pages/libraries/[id]/books.vue @@ -9,4 +9,5 @@ series.vue meta: requiresRole: USER + scrollable: true diff --git a/next-ui/src/pages/libraries/[id]/collections.vue b/next-ui/src/pages/libraries/[id]/collections.vue index 64e70f3e..de17f29a 100644 --- a/next-ui/src/pages/libraries/[id]/collections.vue +++ b/next-ui/src/pages/libraries/[id]/collections.vue @@ -10,4 +10,5 @@ meta: requiresRole: USER + scrollable: true diff --git a/next-ui/src/pages/libraries/[id]/readlists.vue b/next-ui/src/pages/libraries/[id]/readlists.vue index 878f4e9c..b324c27b 100644 --- a/next-ui/src/pages/libraries/[id]/readlists.vue +++ b/next-ui/src/pages/libraries/[id]/readlists.vue @@ -10,4 +10,5 @@ meta: requiresRole: USER + scrollable: true diff --git a/next-ui/src/pages/libraries/[id]/series.vue b/next-ui/src/pages/libraries/[id]/series.vue index 807678da..92c1fab5 100644 --- a/next-ui/src/pages/libraries/[id]/series.vue +++ b/next-ui/src/pages/libraries/[id]/series.vue @@ -123,4 +123,5 @@ watch(series, (newSeries) => { meta: requiresRole: USER + scrollable: true diff --git a/next-ui/src/plugins/index.ts b/next-ui/src/plugins/index.ts index 548cdd86..6f265c52 100644 --- a/next-ui/src/plugins/index.ts +++ b/next-ui/src/plugins/index.ts @@ -18,6 +18,7 @@ import type { App } from 'vue' // Navigation guards import { useLoginGuard } from '@/router/login-guard' import { useRoleGuard } from '@/router/role-guard' +import { useScroll } from '@/router/scroll' export function registerPlugins(app: App) { app @@ -39,4 +40,5 @@ export function registerPlugins(app: App) { // register navigation guards useLoginGuard(router) useRoleGuard(router) + useScroll(router) } diff --git a/next-ui/src/router/index.ts b/next-ui/src/router/index.ts index e1e4d436..c1777017 100644 --- a/next-ui/src/router/index.ts +++ b/next-ui/src/router/index.ts @@ -9,11 +9,24 @@ import { createRouter, createWebHistory } from 'vue-router' import { setupLayouts } from 'virtual:generated-layouts' import { routes } from 'vue-router/auto-routes' +import { scrollerQuery, scrollMap } from '@/router/scroll' + const router = createRouter({ history: createWebHistory( import.meta.env.PROD ? window.resourceBaseUrl : import.meta.env.BASE_URL, ), routes: setupLayouts(routes), + scrollBehavior(to) { + if (scrollMap.has(to.path)) { + const scrollTo = scrollMap.get(to.path) + return new Promise((resolve) => { + void nextTick(() => { + document.querySelector(scrollerQuery)?.scrollTo(0, scrollTo ?? 0) + resolve(false) + }) + }) + } + }, }) // Workaround for https://github.com/vitejs/vite/issues/11804 diff --git a/next-ui/src/router/login-guard.ts b/next-ui/src/router/login-guard.ts index f7da1e9c..fed938f5 100644 --- a/next-ui/src/router/login-guard.ts +++ b/next-ui/src/router/login-guard.ts @@ -1,9 +1,11 @@ import type { Router } from 'vue-router' import { useCurrentUser } from '@/colada/users' -// check if the user is authenticated before navigating to any page -// the authentication is cached by Pinia Colada -// redirect to the startup page if not authenticated +/** + * Check if the user is authenticated before navigating to any page. + * The authentication is cached by Pinia Colada. + * Redirect to the startup page if not authenticated. + */ export function useLoginGuard(router: Router) { router.beforeEach((to) => { if (!to.meta.noAuth) { diff --git a/next-ui/src/router/role-guard.ts b/next-ui/src/router/role-guard.ts index 1c3baa46..6ab75f66 100644 --- a/next-ui/src/router/role-guard.ts +++ b/next-ui/src/router/role-guard.ts @@ -1,9 +1,11 @@ import type { Router } from 'vue-router' import { useCurrentUser } from '@/colada/users' -// check if the user has the necessary role before navigating to restricted pages -// the authentication is cached by Pinia Colada -// redirect to the home page in case of insufficient permissions +/** + * Check if the user has the necessary role before navigating to restricted pages. + * The authentication is cached by Pinia Colada. + * Redirect to the home page in case of insufficient permissions. + */ export function useRoleGuard(router: Router) { router.beforeEach((to) => { if (to.meta.requiresRole) { diff --git a/next-ui/src/router/scroll.ts b/next-ui/src/router/scroll.ts new file mode 100644 index 00000000..269ec2f1 --- /dev/null +++ b/next-ui/src/router/scroll.ts @@ -0,0 +1,23 @@ +import type { Router } from 'vue-router' + +/** + * Stores the `scrollTop` position per `route.path` + */ +export const scrollMap = new Map() +/** + * Query to be used with `document.querySelector()` to get the main scroller. + */ +export const scrollerQuery = '.v-main__scroller' + +/** + * Persist the scroll position when navigating away from a page. + * Scroll restoration is handled in the router's `scrollBehavior` function. + */ +export function useScroll(router: Router) { + router.afterEach((to, from) => { + if (from.meta.scrollable) { + const scroller = document.querySelector(scrollerQuery) + if (scroller) scrollMap.set(from.path, scroller.scrollTop) + } + }) +} diff --git a/next-ui/src/types/RouterMeta.d.ts b/next-ui/src/types/RouterMeta.d.ts index fe2086c8..ab0337fb 100644 --- a/next-ui/src/types/RouterMeta.d.ts +++ b/next-ui/src/types/RouterMeta.d.ts @@ -11,5 +11,9 @@ declare module 'vue-router' { interface RouteMeta { noAuth?: boolean requiresRole?: UserRoles + /** + * Indicates whether the scroll position should be persisted and restored for this page. + */ + scrollable?: boolean } }