🐛(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.
This commit is contained in:
@@ -25,6 +25,7 @@ and this project adheres to
|
|||||||
- ⚗️(service-worker) remove index from cache first strategy #1395
|
- ⚗️(service-worker) remove index from cache first strategy #1395
|
||||||
- 🐛(frontend) fix 404 page when reload 403 page #1402
|
- 🐛(frontend) fix 404 page when reload 403 page #1402
|
||||||
- 🐛(frontend) fix legacy role computation #1376
|
- 🐛(frontend) fix legacy role computation #1376
|
||||||
|
- 🐛(frontend) scroll back to top when navigate to a document #1406
|
||||||
|
|
||||||
## [3.7.0] - 2025-09-12
|
## [3.7.0] - 2025-09-12
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import {
|
|||||||
overrideConfig,
|
overrideConfig,
|
||||||
verifyDocName,
|
verifyDocName,
|
||||||
} from './utils-common';
|
} 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 }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
@@ -86,8 +87,7 @@ test.describe('Doc Editor', () => {
|
|||||||
// Is connected
|
// Is connected
|
||||||
let framesentPromise = webSocket.waitForEvent('framesent');
|
let framesentPromise = webSocket.waitForEvent('framesent');
|
||||||
|
|
||||||
await page.locator('.ProseMirror.bn-editor').click();
|
await writeInEditor({ page, text: 'Hello World' });
|
||||||
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
|
|
||||||
|
|
||||||
let framesent = await framesentPromise;
|
let framesent = await framesentPromise;
|
||||||
expect(framesent.payload).not.toBeNull();
|
expect(framesent.payload).not.toBeNull();
|
||||||
@@ -505,10 +505,7 @@ test.describe('Doc Editor', () => {
|
|||||||
|
|
||||||
await verifyDocName(page, randomDoc);
|
await verifyDocName(page, randomDoc);
|
||||||
|
|
||||||
const editor = page.locator('.ProseMirror.bn-editor');
|
const editor = await openSuggestionMenu({ page });
|
||||||
|
|
||||||
await editor.click();
|
|
||||||
await editor.locator('.bn-block-outer').last().fill('/');
|
|
||||||
await page.getByText('Embedded file').click();
|
await page.getByText('Embedded file').click();
|
||||||
await page.getByText('Upload 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 }) => {
|
test('it checks if callout custom block', async ({ page, browserName }) => {
|
||||||
await createDoc(page, 'doc-toolbar', browserName, 1);
|
await createDoc(page, 'doc-toolbar', browserName, 1);
|
||||||
|
|
||||||
const editor = page.locator('.ProseMirror');
|
await openSuggestionMenu({ page });
|
||||||
await editor.click();
|
|
||||||
await page.locator('.bn-block-outer').last().fill('/');
|
|
||||||
await page.getByText('Add a callout block').click();
|
await page.getByText('Add a callout block').click();
|
||||||
|
|
||||||
const calloutBlock = page
|
const calloutBlock = page
|
||||||
@@ -791,4 +786,52 @@ test.describe('Doc Editor', () => {
|
|||||||
),
|
),
|
||||||
).toBeVisible();
|
).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
test.describe('Doc Table Content', () => {
|
test.describe('Doc Table Content', () => {
|
||||||
test('it checks the doc table content', async ({ page, browserName }) => {
|
test('it checks the doc table content', async ({ page, browserName }) => {
|
||||||
test.setTimeout(60000);
|
|
||||||
|
|
||||||
const [randomDoc] = await createDoc(
|
const [randomDoc] = await createDoc(
|
||||||
page,
|
page,
|
||||||
'doc-table-content',
|
'doc-table-content',
|
||||||
|
|||||||
27
src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts
Normal file
27
src/frontend/apps/e2e/__tests__/app-impress/utils-editor.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { Page, expect } from '@playwright/test';
|
|||||||
import {
|
import {
|
||||||
randomName,
|
randomName,
|
||||||
updateDocTitle,
|
updateDocTitle,
|
||||||
|
verifyDocName,
|
||||||
waitForResponseCreateDoc,
|
waitForResponseCreateDoc,
|
||||||
} from './utils-common';
|
} from './utils-common';
|
||||||
|
|
||||||
@@ -65,3 +66,15 @@ export const clickOnAddRootSubPage = async (page: Page) => {
|
|||||||
await rootItem.hover();
|
await rootItem.hover();
|
||||||
await rootItem.getByRole('button', { name: /add subpage/i }).click();
|
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);
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
import { KEY_AUTH, setAuthUrl, useAuth } from '@/features/auth';
|
||||||
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
import { getDocChildren, subPageToTree } from '@/features/docs/doc-tree/';
|
||||||
import { MainLayout } from '@/layouts';
|
import { MainLayout } from '@/layouts';
|
||||||
|
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||||
import { useBroadcastStore } from '@/stores';
|
import { useBroadcastStore } from '@/stores';
|
||||||
import { NextPageWithLayout } from '@/types/next';
|
import { NextPageWithLayout } from '@/types/next';
|
||||||
|
|
||||||
@@ -89,6 +90,26 @@ const DocPage = ({ id }: DocProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { authenticated } = useAuth();
|
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
|
// Invalidate when provider store reports a lost connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasLostConnection && doc?.id) {
|
if (hasLostConnection && doc?.id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user