From c23ff546d8c78177304812dd06283a3a32ac98c1 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 18 Sep 2025 17:40:47 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20scroll=20back=20to=20t?= =?UTF-8?q?op=20when=20navigate=20to=20a=20document?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When navigating to a new document, the scroll position was preserved. This commit changes this behavior to scroll back to the top of the page when navigating to a new document. --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-editor.spec.ts | 63 ++++++++++++++++--- .../app-impress/doc-table-content.spec.ts | 2 - .../e2e/__tests__/app-impress/utils-editor.ts | 27 ++++++++ .../__tests__/app-impress/utils-sub-pages.ts | 13 ++++ .../impress/src/pages/docs/[id]/index.tsx | 21 +++++++ 6 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 84d2978a00..9103b8fb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to - ⚗️(service-worker) remove index from cache first strategy #1395 - 🐛(frontend) fix 404 page when reload 403 page #1402 - 🐛(frontend) fix legacy role computation #1376 +- 🐛(frontend) scroll back to top when navigate to a document #1406 ## [3.7.0] - 2025-09-12 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts index 552680beff..c371c2c4e1 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts @@ -11,7 +11,8 @@ import { overrideConfig, verifyDocName, } from './utils-common'; -import { createRootSubPage } from './utils-sub-pages'; +import { getEditor, openSuggestionMenu, writeInEditor } from './utils-editor'; +import { createRootSubPage, navigateToPageFromTree } from './utils-sub-pages'; test.beforeEach(async ({ page }) => { await page.goto('/'); @@ -86,8 +87,7 @@ test.describe('Doc Editor', () => { // Is connected let framesentPromise = webSocket.waitForEvent('framesent'); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World'); + await writeInEditor({ page, text: 'Hello World' }); let framesent = await framesentPromise; expect(framesent.payload).not.toBeNull(); @@ -505,10 +505,7 @@ test.describe('Doc Editor', () => { await verifyDocName(page, randomDoc); - const editor = page.locator('.ProseMirror.bn-editor'); - - await editor.click(); - await editor.locator('.bn-block-outer').last().fill('/'); + const editor = await openSuggestionMenu({ page }); await page.getByText('Embedded file').click(); await page.getByText('Upload file').click(); @@ -675,9 +672,7 @@ test.describe('Doc Editor', () => { test('it checks if callout custom block', async ({ page, browserName }) => { await createDoc(page, 'doc-toolbar', browserName, 1); - const editor = page.locator('.ProseMirror'); - await editor.click(); - await page.locator('.bn-block-outer').last().fill('/'); + await openSuggestionMenu({ page }); await page.getByText('Add a callout block').click(); const calloutBlock = page @@ -791,4 +786,52 @@ test.describe('Doc Editor', () => { ), ).toBeVisible(); }); + + test('it checks multiple big doc scroll to the top', async ({ + page, + browserName, + }) => { + const [randomDoc] = await createDoc(page, 'doc-scroll', browserName, 1); + + for (let i = 0; i < 15; i++) { + await page.keyboard.press('Enter'); + await writeInEditor({ page, text: 'Hello Parent ' + i }); + } + + const editor = await getEditor({ page }); + await expect( + editor.getByText('Hello Parent 1', { exact: true }), + ).not.toBeInViewport(); + await expect(editor.getByText('Hello Parent 14')).toBeInViewport(); + + const { name: docChild } = await createRootSubPage( + page, + browserName, + 'doc-scroll-child', + ); + + for (let i = 0; i < 15; i++) { + await page.keyboard.press('Enter'); + await writeInEditor({ page, text: 'Hello Child ' + i }); + } + + await expect( + editor.getByText('Hello Child 1', { exact: true }), + ).not.toBeInViewport(); + await expect(editor.getByText('Hello Child 14')).toBeInViewport(); + + await navigateToPageFromTree({ page, title: randomDoc }); + + await expect( + editor.getByText('Hello Parent 1', { exact: true }), + ).toBeInViewport(); + await expect(editor.getByText('Hello Parent 14')).not.toBeInViewport(); + + await navigateToPageFromTree({ page, title: docChild }); + + await expect( + editor.getByText('Hello Child 1', { exact: true }), + ).toBeInViewport(); + await expect(editor.getByText('Hello Child 14')).not.toBeInViewport(); + }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts index 3f293cfaf3..cf1670fd4e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts @@ -8,8 +8,6 @@ test.beforeEach(async ({ page }) => { test.describe('Doc Table Content', () => { test('it checks the doc table content', async ({ page, browserName }) => { - test.setTimeout(60000); - const [randomDoc] = await createDoc( page, 'doc-table-content', diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts new file mode 100644 index 0000000000..8bf5f106c4 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts @@ -0,0 +1,27 @@ +import { Page } from '@playwright/test'; + +export const getEditor = async ({ page }: { page: Page }) => { + const editor = page.locator('.ProseMirror'); + await editor.click(); + return editor; +}; + +export const openSuggestionMenu = async ({ page }: { page: Page }) => { + const editor = await getEditor({ page }); + await editor.click(); + await page.locator('.bn-block-outer').last().fill('/'); + + return editor; +}; + +export const writeInEditor = async ({ + page, + text, +}: { + page: Page; + text: string; +}) => { + const editor = await getEditor({ page }); + editor.locator('.bn-block-outer').last().fill(text); + return editor; +}; diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-sub-pages.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-sub-pages.ts index 81d76d9c1a..6e3f7e513e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-sub-pages.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-sub-pages.ts @@ -3,6 +3,7 @@ import { Page, expect } from '@playwright/test'; import { randomName, updateDocTitle, + verifyDocName, waitForResponseCreateDoc, } from './utils-common'; @@ -65,3 +66,15 @@ export const clickOnAddRootSubPage = async (page: Page) => { await rootItem.hover(); await rootItem.getByRole('button', { name: /add subpage/i }).click(); }; + +export const navigateToPageFromTree = async ({ + page, + title, +}: { + page: Page; + title: string; +}) => { + const docTree = page.getByTestId('doc-tree'); + await docTree.getByText(title).click(); + await verifyDocName(page, title); +}; diff --git a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx index 86ce81e155..075b241ed8 100644 --- a/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/[id]/index.tsx @@ -20,6 +20,7 @@ import { import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth'; import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/'; import { MainLayout } from '@/layouts'; +import { MAIN_LAYOUT_ID } from '@/layouts/conf'; import { useBroadcastStore } from '@/stores'; import { NextPageWithLayout } from '@/types/next'; @@ -89,6 +90,26 @@ const DocPage = ({ id }: DocProps) => { const { t } = useTranslation(); const { authenticated } = useAuth(); + /** + * Scroll to top when navigating to a new document + * We use a timeout to ensure the scroll happens after the layout has updated. + */ + useEffect(() => { + let timeoutId: NodeJS.Timeout | undefined; + const mainElement = document.getElementById(MAIN_LAYOUT_ID); + if (mainElement) { + timeoutId = setTimeout(() => { + mainElement.scrollTop = 0; + }, 150); + } + + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [id]); + // Invalidate when provider store reports a lost connection useEffect(() => { if (hasLostConnection && doc?.id) {