Skip to content

Commit c23ff54

Browse files
committed
🐛(frontend) scroll back to top when navigate to a document
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.
1 parent a751f12 commit c23ff54

File tree

6 files changed

+115
-12
lines changed

6 files changed

+115
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to
2525
- ⚗️(service-worker) remove index from cache first strategy #1395
2626
- 🐛(frontend) fix 404 page when reload 403 page #1402
2727
- 🐛(frontend) fix legacy role computation #1376
28+
- 🐛(frontend) scroll back to top when navigate to a document #1406
2829

2930
## [3.7.0] - 2025-09-12
3031

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
overrideConfig,
1212
verifyDocName,
1313
} from './utils-common';
14-
import { createRootSubPage } from './utils-sub-pages';
14+
import { getEditor, openSuggestionMenu, writeInEditor } from './utils-editor';
15+
import { createRootSubPage, navigateToPageFromTree } from './utils-sub-pages';
1516

1617
test.beforeEach(async ({ page }) => {
1718
await page.goto('/');
@@ -86,8 +87,7 @@ test.describe('Doc Editor', () => {
8687
// Is connected
8788
let framesentPromise = webSocket.waitForEvent('framesent');
8889

89-
await page.locator('.ProseMirror.bn-editor').click();
90-
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
90+
await writeInEditor({ page, text: 'Hello World' });
9191

9292
let framesent = await framesentPromise;
9393
expect(framesent.payload).not.toBeNull();
@@ -505,10 +505,7 @@ test.describe('Doc Editor', () => {
505505

506506
await verifyDocName(page, randomDoc);
507507

508-
const editor = page.locator('.ProseMirror.bn-editor');
509-
510-
await editor.click();
511-
await editor.locator('.bn-block-outer').last().fill('/');
508+
const editor = await openSuggestionMenu({ page });
512509
await page.getByText('Embedded file').click();
513510
await page.getByText('Upload file').click();
514511

@@ -675,9 +672,7 @@ test.describe('Doc Editor', () => {
675672
test('it checks if callout custom block', async ({ page, browserName }) => {
676673
await createDoc(page, 'doc-toolbar', browserName, 1);
677674

678-
const editor = page.locator('.ProseMirror');
679-
await editor.click();
680-
await page.locator('.bn-block-outer').last().fill('/');
675+
await openSuggestionMenu({ page });
681676
await page.getByText('Add a callout block').click();
682677

683678
const calloutBlock = page
@@ -791,4 +786,52 @@ test.describe('Doc Editor', () => {
791786
),
792787
).toBeVisible();
793788
});
789+
790+
test('it checks multiple big doc scroll to the top', async ({
791+
page,
792+
browserName,
793+
}) => {
794+
const [randomDoc] = await createDoc(page, 'doc-scroll', browserName, 1);
795+
796+
for (let i = 0; i < 15; i++) {
797+
await page.keyboard.press('Enter');
798+
await writeInEditor({ page, text: 'Hello Parent ' + i });
799+
}
800+
801+
const editor = await getEditor({ page });
802+
await expect(
803+
editor.getByText('Hello Parent 1', { exact: true }),
804+
).not.toBeInViewport();
805+
await expect(editor.getByText('Hello Parent 14')).toBeInViewport();
806+
807+
const { name: docChild } = await createRootSubPage(
808+
page,
809+
browserName,
810+
'doc-scroll-child',
811+
);
812+
813+
for (let i = 0; i < 15; i++) {
814+
await page.keyboard.press('Enter');
815+
await writeInEditor({ page, text: 'Hello Child ' + i });
816+
}
817+
818+
await expect(
819+
editor.getByText('Hello Child 1', { exact: true }),
820+
).not.toBeInViewport();
821+
await expect(editor.getByText('Hello Child 14')).toBeInViewport();
822+
823+
await navigateToPageFromTree({ page, title: randomDoc });
824+
825+
await expect(
826+
editor.getByText('Hello Parent 1', { exact: true }),
827+
).toBeInViewport();
828+
await expect(editor.getByText('Hello Parent 14')).not.toBeInViewport();
829+
830+
await navigateToPageFromTree({ page, title: docChild });
831+
832+
await expect(
833+
editor.getByText('Hello Child 1', { exact: true }),
834+
).toBeInViewport();
835+
await expect(editor.getByText('Hello Child 14')).not.toBeInViewport();
836+
});
794837
});

src/frontend/apps/e2e/__tests__/app-impress/doc-table-content.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ test.beforeEach(async ({ page }) => {
88

99
test.describe('Doc Table Content', () => {
1010
test('it checks the doc table content', async ({ page, browserName }) => {
11-
test.setTimeout(60000);
12-
1311
const [randomDoc] = await createDoc(
1412
page,
1513
'doc-table-content',
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Page } from '@playwright/test';
2+
3+
export const getEditor = async ({ page }: { page: Page }) => {
4+
const editor = page.locator('.ProseMirror');
5+
await editor.click();
6+
return editor;
7+
};
8+
9+
export const openSuggestionMenu = async ({ page }: { page: Page }) => {
10+
const editor = await getEditor({ page });
11+
await editor.click();
12+
await page.locator('.bn-block-outer').last().fill('/');
13+
14+
return editor;
15+
};
16+
17+
export const writeInEditor = async ({
18+
page,
19+
text,
20+
}: {
21+
page: Page;
22+
text: string;
23+
}) => {
24+
const editor = await getEditor({ page });
25+
editor.locator('.bn-block-outer').last().fill(text);
26+
return editor;
27+
};

src/frontend/apps/e2e/__tests__/app-impress/utils-sub-pages.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Page, expect } from '@playwright/test';
33
import {
44
randomName,
55
updateDocTitle,
6+
verifyDocName,
67
waitForResponseCreateDoc,
78
} from './utils-common';
89

@@ -65,3 +66,15 @@ export const clickOnAddRootSubPage = async (page: Page) => {
6566
await rootItem.hover();
6667
await rootItem.getByRole('button', { name: /add subpage/i }).click();
6768
};
69+
70+
export const navigateToPageFromTree = async ({
71+
page,
72+
title,
73+
}: {
74+
page: Page;
75+
title: string;
76+
}) => {
77+
const docTree = page.getByTestId('doc-tree');
78+
await docTree.getByText(title).click();
79+
await verifyDocName(page, title);
80+
};

src/frontend/apps/impress/src/pages/docs/[id]/index.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
2121
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
2222
import { MainLayout } from '@/layouts';
23+
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
2324
import { useBroadcastStore } from '@/stores';
2425
import { NextPageWithLayout } from '@/types/next';
2526

@@ -89,6 +90,26 @@ const DocPage = ({ id }: DocProps) => {
8990
const { t } = useTranslation();
9091
const { authenticated } = useAuth();
9192

93+
/**
94+
* Scroll to top when navigating to a new document
95+
* We use a timeout to ensure the scroll happens after the layout has updated.
96+
*/
97+
useEffect(() => {
98+
let timeoutId: NodeJS.Timeout | undefined;
99+
const mainElement = document.getElementById(MAIN_LAYOUT_ID);
100+
if (mainElement) {
101+
timeoutId = setTimeout(() => {
102+
mainElement.scrollTop = 0;
103+
}, 150);
104+
}
105+
106+
return () => {
107+
if (timeoutId) {
108+
clearTimeout(timeoutId);
109+
}
110+
};
111+
}, [id]);
112+
92113
// Invalidate when provider store reports a lost connection
93114
useEffect(() => {
94115
if (hasLostConnection && doc?.id) {

0 commit comments

Comments
 (0)