Skip to content

Commit c822594

Browse files
committed
feat(core): mode in query string (#7904)
1 parent 83716c2 commit c822594

File tree

16 files changed

+174
-29
lines changed

16 files changed

+174
-29
lines changed

packages/frontend/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"mixpanel-browser": "^2.49.0",
8282
"nanoid": "^5.0.7",
8383
"next-themes": "^0.3.0",
84+
"query-string": "^9.1.0",
8485
"react": "18.3.1",
8586
"react-dom": "18.3.1",
8687
"react-error-boundary": "^4.0.13",

packages/frontend/core/src/modules/workbench/entities/view.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Entity, LiveData } from '@toeverything/infra';
22
import type { Location, To } from 'history';
3+
import { isEqual } from 'lodash-es';
4+
import queryString from 'query-string';
35
import { Observable } from 'rxjs';
46

57
import { createNavigableHistory } from '../../../utils/navigable-history';
@@ -77,6 +79,52 @@ export class View extends Entity<{
7779

7880
icon$ = new LiveData(this.props.icon ?? 'allDocs');
7981

82+
queryString$<T extends Record<string, unknown>>({
83+
parseNumbers = true,
84+
}: { parseNumbers?: boolean } = {}) {
85+
return this.location$.map(
86+
location =>
87+
queryString.parse(location.search, {
88+
parseBooleans: true,
89+
parseNumbers: parseNumbers,
90+
}) as Partial<T>
91+
);
92+
}
93+
94+
updateQueryString<T extends Record<string, unknown>>(
95+
patch: Partial<T>,
96+
{
97+
forceUpdate,
98+
parseNumbers,
99+
replace,
100+
}: {
101+
forceUpdate?: boolean;
102+
parseNumbers?: boolean;
103+
replace?: boolean;
104+
} = {}
105+
) {
106+
const oldQueryStrings = queryString.parse(location.search, {
107+
parseBooleans: true,
108+
parseNumbers: parseNumbers,
109+
});
110+
const newQueryStrings = { ...oldQueryStrings, ...patch };
111+
112+
if (forceUpdate || !isEqual(oldQueryStrings, newQueryStrings)) {
113+
const search = queryString.stringify(newQueryStrings);
114+
115+
const newState = {
116+
...this.history.location,
117+
search,
118+
};
119+
120+
if (replace) {
121+
this.history.replace(newState);
122+
} else {
123+
this.history.push(newState);
124+
}
125+
}
126+
}
127+
80128
push(path: To) {
81129
this.history.push(path);
82130
}

packages/frontend/core/src/pages/workspace/detail-page/detail-page.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Editor } from '@affine/core/modules/editor';
1010
import { EditorService, EditorsService } from '@affine/core/modules/editor';
1111
import { RecentDocsService } from '@affine/core/modules/quicksearch';
1212
import { ViewService } from '@affine/core/modules/workbench/services/view';
13-
import type { PageRootService } from '@blocksuite/blocks';
13+
import type { DocMode, PageRootService } from '@blocksuite/blocks';
1414
import {
1515
BookmarkBlockService,
1616
customImageProxyMiddleware,
@@ -330,9 +330,25 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
330330
const docRecordList = docsService.list;
331331
const docListReady = useLiveData(docRecordList.isReady$);
332332
const docRecord = useLiveData(docRecordList.doc$(pageId));
333+
const viewService = useService(ViewService);
334+
335+
const queryString = useLiveData(
336+
viewService.view.queryString$<{
337+
mode?: string;
338+
}>()
339+
);
340+
341+
const queryStringMode =
342+
queryString.mode && ['edgeless', 'page'].includes(queryString.mode)
343+
? (queryString.mode as DocMode)
344+
: null;
345+
346+
// We only read the querystring mode when entering, so use useState here.
347+
const [initialQueryStringMode] = useState(() => queryStringMode);
333348

334349
const [doc, setDoc] = useState<Doc | null>(null);
335350
const [editor, setEditor] = useState<Editor | null>(null);
351+
const editorMode = useLiveData(editor?.mode$);
336352

337353
useLayoutEffect(() => {
338354
if (!docRecord) {
@@ -351,12 +367,26 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
351367
}
352368
const editor = doc.scope
353369
.get(EditorsService)
354-
.createEditor(doc.getPrimaryMode() || 'page');
370+
.createEditor(initialQueryStringMode || doc.getPrimaryMode() || 'page');
355371
setEditor(editor);
356372
return () => {
357373
editor.dispose();
358374
};
359-
}, [doc]);
375+
}, [doc, initialQueryStringMode]);
376+
377+
// update editor mode to queryString
378+
useEffect(() => {
379+
if (editorMode) {
380+
viewService.view.updateQueryString(
381+
{
382+
mode: editorMode,
383+
},
384+
{
385+
replace: true,
386+
}
387+
);
388+
}
389+
}, [editorMode, viewService.view]);
360390

361391
// set sync engine priority target
362392
useEffect(() => {

packages/frontend/graphql/src/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ export enum ErrorNames {
310310
MAILER_SERVICE_IS_NOT_CONFIGURED = 'MAILER_SERVICE_IS_NOT_CONFIGURED',
311311
MEMBER_QUOTA_EXCEEDED = 'MEMBER_QUOTA_EXCEEDED',
312312
MISSING_OAUTH_QUERY_PARAMETER = 'MISSING_OAUTH_QUERY_PARAMETER',
313+
NOT_FOUND = 'NOT_FOUND',
313314
NOT_IN_WORKSPACE = 'NOT_IN_WORKSPACE',
314315
NO_COPILOT_PROVIDER_AVAILABLE = 'NO_COPILOT_PROVIDER_AVAILABLE',
315316
OAUTH_ACCOUNT_ALREADY_CONNECTED = 'OAUTH_ACCOUNT_ALREADY_CONNECTED',

tests/affine-local/e2e/doc-info-modal.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
filterTags,
1717
removeSelectedTag,
1818
} from '@affine-test/kit/utils/properties';
19+
import { getCurrentDocIdFromUrl } from '@affine-test/kit/utils/url';
1920
import { expect, type Page } from '@playwright/test';
2021

2122
const searchAndCreateTag = async (page: Page, name: string) => {
@@ -64,7 +65,7 @@ test('New a page and open it ,then open info modal in the title bar more action
6465
});
6566

6667
test('New a page, then open info modal from all doc', async ({ page }) => {
67-
const newPageId = page.url().split('/').reverse()[0];
68+
const newPageId = getCurrentDocIdFromUrl(page);
6869

6970
await page.getByTestId('all-pages').click();
7071
const cell = getPageByTitle(page, 'this is a new page');
@@ -83,7 +84,7 @@ test('New a page, then open info modal from all doc', async ({ page }) => {
8384
test('New a page and add to favourites, then open info modal from sidebar', async ({
8485
page,
8586
}) => {
86-
const newPageId = page.url().split('/').reverse()[0];
87+
const newPageId = getCurrentDocIdFromUrl(page);
8788

8889
await clickPageMoreActions(page);
8990
await page.getByTestId('editor-option-menu-favorite').click();

tests/affine-local/e2e/drag-page.spec.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import {
88
waitForEditorLoad,
99
} from '@affine-test/kit/utils/page-logic';
1010
import { clickSideBarAllPageButton } from '@affine-test/kit/utils/sidebar';
11+
import {
12+
getCurrentCollectionIdFromUrl,
13+
getCurrentDocIdFromUrl,
14+
} from '@affine-test/kit/utils/url';
1115
import type { Locator, Page } from '@playwright/test';
1216
import { expect } from '@playwright/test';
1317

@@ -32,7 +36,7 @@ const createCollection = async (page: Page, name: string) => {
3236
await expect(input).toBeVisible();
3337
await input.fill(name);
3438
await page.getByTestId('save-collection').click();
35-
const newCollectionId = page.url().split('/').reverse()[0];
39+
const newCollectionId = getCurrentCollectionIdFromUrl(page);
3640
const collection = page.getByTestId(`explorer-collection-${newCollectionId}`);
3741
await expect(collection).toBeVisible();
3842
return collection;
@@ -85,7 +89,7 @@ test('drag a page from "All pages" list to favourites, then drag to trash', asyn
8589
const title = 'this is a new page to drag';
8690
await waitForEditorLoad(page);
8791
await createPage(page, title);
88-
const pageId = page.url().split('/').reverse()[0];
92+
const pageId = getCurrentDocIdFromUrl(page);
8993
await clickSideBarAllPageButton(page);
9094
await page.waitForTimeout(500);
9195

@@ -133,7 +137,7 @@ test('drag a page from favourites to collection', async ({ page }) => {
133137
const title = 'this is a new page to drag';
134138
await createPage(page, title);
135139

136-
const pageId = page.url().split('/').reverse()[0];
140+
const pageId = getCurrentDocIdFromUrl(page);
137141
await clickSideBarAllPageButton(page);
138142
await page.waitForTimeout(500);
139143

@@ -152,7 +156,7 @@ test('drag a collection to favourites', async ({ page }) => {
152156
await clickSideBarAllPageButton(page);
153157
await page.waitForTimeout(500);
154158
const collection = await createCollection(page, 'test collection');
155-
const collectionId = page.url().split('/').reverse()[0];
159+
const collectionId = getCurrentCollectionIdFromUrl(page);
156160
await dragToFavourites(page, collection, collectionId, 'collection');
157161
});
158162

@@ -167,7 +171,7 @@ test('items in favourites can be reordered by dragging', async ({ page }) => {
167171

168172
{
169173
const collection = await createCollection(page, 'test collection');
170-
const collectionId = page.url().split('/').reverse()[0];
174+
const collectionId = getCurrentCollectionIdFromUrl(page);
171175
await dragToFavourites(page, collection, collectionId, 'collection');
172176
}
173177

tests/affine-local/e2e/local-first-delete-page.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getPageOperationButton,
88
waitForEditorLoad,
99
} from '@affine-test/kit/utils/page-logic';
10+
import { getCurrentDocIdFromUrl } from '@affine-test/kit/utils/url';
1011
import { expect } from '@playwright/test';
1112

1213
test('page delete -> refresh page -> it should be disappear', async ({
@@ -18,7 +19,7 @@ test('page delete -> refresh page -> it should be disappear', async ({
1819
await clickNewPageButton(page);
1920
await getBlockSuiteEditorTitle(page).click();
2021
await getBlockSuiteEditorTitle(page).fill('this is a new page delete');
21-
const newPageId = page.url().split('/').reverse()[0];
22+
const newPageId = getCurrentDocIdFromUrl(page);
2223
await page.getByTestId('all-pages').click();
2324
const cell = page.getByRole('cell', {
2425
name: 'this is a new page delete',
@@ -54,7 +55,7 @@ test('page delete -> create new page -> refresh page -> new page should be appea
5455
await clickNewPageButton(page);
5556
await getBlockSuiteEditorTitle(page).click();
5657
await getBlockSuiteEditorTitle(page).fill('this is a new page delete');
57-
const newPageDeleteId = page.url().split('/').reverse()[0];
58+
const newPageDeleteId = getCurrentDocIdFromUrl(page);
5859
await page.getByTestId('all-pages').click();
5960
const cellDelete = page.getByRole('cell', {
6061
name: 'this is a new page delete',
@@ -80,13 +81,13 @@ test('page delete -> create new page -> refresh page -> new page should be appea
8081
await getBlockSuiteEditorTitle(page).click();
8182
await getBlockSuiteEditorTitle(page).fill('this is a new page1');
8283
await page.waitForTimeout(1000);
83-
const newPageId1 = page.url().split('/').reverse()[0];
84+
const newPageId1 = getCurrentDocIdFromUrl(page);
8485
await page.getByTestId('all-pages').click();
8586
await clickNewPageButton(page);
8687
await getBlockSuiteEditorTitle(page).click();
8788
await getBlockSuiteEditorTitle(page).fill('this is a new page2');
8889
await page.waitForTimeout(1000);
89-
const newPageId2 = page.url().split('/').reverse()[0];
90+
const newPageId2 = getCurrentDocIdFromUrl(page);
9091
await page.getByTestId('all-pages').click();
9192
await page.reload();
9293
await getPageItem(page, newPageId1).click();
@@ -109,13 +110,13 @@ test('delete multiple pages -> create multiple pages -> refresh', async ({
109110
await clickNewPageButton(page);
110111
await getBlockSuiteEditorTitle(page).click();
111112
await getBlockSuiteEditorTitle(page).fill('this is a new page1');
112-
const newPageId1 = page.url().split('/').reverse()[0];
113+
const newPageId1 = getCurrentDocIdFromUrl(page);
113114
await page.getByTestId('all-pages').click();
114115
// create 2nd page
115116
await clickNewPageButton(page);
116117
await getBlockSuiteEditorTitle(page).click();
117118
await getBlockSuiteEditorTitle(page).fill('this is a new page2');
118-
const newPageId2 = page.url().split('/').reverse()[0];
119+
const newPageId2 = getCurrentDocIdFromUrl(page);
119120
await page.getByTestId('all-pages').click();
120121

121122
// 1st cell to be deleted

tests/affine-local/e2e/local-first-favorites-items.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
waitForEditorLoad,
1111
waitForEmptyEditor,
1212
} from '@affine-test/kit/utils/page-logic';
13+
import { getCurrentDocIdFromUrl } from '@affine-test/kit/utils/url';
1314
import { expect } from '@playwright/test';
1415

1516
test('Show favorite items in sidebar', async ({ page, workspace }) => {
@@ -18,7 +19,7 @@ test('Show favorite items in sidebar', async ({ page, workspace }) => {
1819
await clickNewPageButton(page);
1920
await getBlockSuiteEditorTitle(page).click();
2021
await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite');
21-
const newPageId = page.url().split('/').reverse()[0];
22+
const newPageId = getCurrentDocIdFromUrl(page);
2223
await page.getByTestId('all-pages').click();
2324
const cell = getPageByTitle(page, 'this is a new page to favorite');
2425
await expect(cell).toBeVisible();
@@ -50,7 +51,7 @@ test('Show favorite reference in sidebar', async ({ page, workspace }) => {
5051

5152
await createLinkedPage(page, 'Another page');
5253

53-
const newPageId = page.url().split('/').reverse()[0];
54+
const newPageId = getCurrentDocIdFromUrl(page);
5455

5556
await clickPageMoreActions(page);
5657

@@ -89,7 +90,7 @@ test("Deleted page's reference will not be shown in sidebar", async ({
8990
await getBlockSuiteEditorTitle(page).click();
9091
await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite');
9192

92-
const newPageId = page.url().split('/').reverse()[0];
93+
const newPageId = getCurrentDocIdFromUrl(page);
9394

9495
// goes to main content
9596
await page.keyboard.press('Enter', { delay: 50 });
@@ -108,7 +109,7 @@ test("Deleted page's reference will not be shown in sidebar", async ({
108109
page.locator('.doc-title-container:has-text("Another page")')
109110
).toBeVisible();
110111

111-
const anotherPageId = page.url().split('/').reverse()[0];
112+
const anotherPageId = getCurrentDocIdFromUrl(page);
112113

113114
const favItemTestId = 'explorer-doc-' + newPageId;
114115

tests/affine-local/e2e/local-first-new-page.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import {
55
getBlockSuiteEditorTitle,
66
waitForEditorLoad,
77
} from '@affine-test/kit/utils/page-logic';
8+
import { getCurrentDocIdFromUrl } from '@affine-test/kit/utils/url';
89
import { expect } from '@playwright/test';
910

1011
test('click btn new page', async ({ page, workspace }) => {
1112
await openHomePage(page);
1213
await waitForEditorLoad(page);
13-
const originPageId = page.url().split('/').reverse()[0];
14+
const originPageId = getCurrentDocIdFromUrl(page);
1415
await clickNewPageButton(page);
15-
const newPageId = page.url().split('/').reverse()[0];
16+
const newPageId = getCurrentDocIdFromUrl(page);
1617
expect(newPageId).not.toBe(originPageId);
1718
const currentWorkspace = await workspace.current();
1819

tests/affine-local/e2e/local-first-openpage-newtab.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getPageOperationButton,
77
waitForEditorLoad,
88
} from '@affine-test/kit/utils/page-logic';
9+
import { getCurrentDocIdFromUrl } from '@affine-test/kit/utils/url';
910
import { expect } from '@playwright/test';
1011

1112
test('click btn new page and open in tab', async ({ page, workspace }) => {
@@ -15,7 +16,7 @@ test('click btn new page and open in tab', async ({ page, workspace }) => {
1516
await getBlockSuiteEditorTitle(page).click();
1617
await getBlockSuiteEditorTitle(page).fill('this is a new page');
1718
const newPageUrl = page.url();
18-
const newPageId = page.url().split('/').reverse()[0];
19+
const newPageId = getCurrentDocIdFromUrl(page);
1920

2021
await page.getByTestId('all-pages').click();
2122

0 commit comments

Comments
 (0)