feat: follow publish mode if no params

This commit is contained in:
DarkSky
2026-04-05 20:43:09 +08:00
parent f81abe692d
commit 64149d909a
4 changed files with 173 additions and 20 deletions
@@ -1,24 +1,21 @@
import { PublicDocMode } from '@affine/graphql';
import { describe, expect, test } from 'vitest';
import { afterEach, describe, expect, test, vi } from 'vitest';
import {
fetchSharedPublishMode,
getResolvedPublishMode,
getSearchWithMode,
parsePublishMode,
} from '../desktop/pages/workspace/share/share-page.utils';
describe('getResolvedPublishMode', () => {
test('prefers the query mode when it is present', () => {
expect(getResolvedPublishMode('edgeless', PublicDocMode.Page)).toBe(
'edgeless'
);
expect(getResolvedPublishMode('page', PublicDocMode.Edgeless)).toBe('page');
expect(getResolvedPublishMode('edgeless', 'page')).toBe('edgeless');
expect(getResolvedPublishMode('page', 'edgeless')).toBe('page');
});
test('falls back to the published public mode for shared docs', () => {
expect(getResolvedPublishMode(null, PublicDocMode.Edgeless)).toBe(
'edgeless'
);
expect(getResolvedPublishMode(null, PublicDocMode.Page)).toBe('page');
expect(getResolvedPublishMode(null, 'edgeless')).toBe('edgeless');
expect(getResolvedPublishMode(null, 'page')).toBe('page');
});
test('defaults to page when no mode is available', () => {
@@ -27,6 +24,66 @@ describe('getResolvedPublishMode', () => {
});
});
describe('parsePublishMode', () => {
test('accepts valid publish modes only', () => {
expect(parsePublishMode('page')).toBe('page');
expect(parsePublishMode('edgeless')).toBe('edgeless');
expect(parsePublishMode('invalid')).toBeNull();
expect(parsePublishMode(null)).toBeNull();
});
});
describe('fetchSharedPublishMode', () => {
afterEach(() => {
vi.restoreAllMocks();
});
test('reads publish mode from the HEAD response', async () => {
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(
new Response(null, {
status: 200,
headers: { 'publish-mode': 'edgeless' },
})
);
await expect(
fetchSharedPublishMode({
serverBaseUrl: 'https://app.affine.pro',
workspaceId: 'workspace-id',
docId: 'doc-id',
})
).resolves.toBe('edgeless');
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock).toHaveBeenCalledWith(
new URL(
'/api/workspaces/workspace-id/docs/doc-id',
'https://app.affine.pro'
),
expect.objectContaining({ method: 'HEAD' })
);
});
test('falls back to GET when HEAD misses the header', async () => {
vi.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce(new Response(null, { status: 200 }))
.mockResolvedValueOnce(
new Response(null, {
status: 200,
headers: { 'publish-mode': 'page' },
})
);
await expect(
fetchSharedPublishMode({
serverBaseUrl: 'https://app.affine.pro',
workspaceId: 'workspace-id',
docId: 'doc-id',
})
).resolves.toBe('page');
expect(globalThis.fetch).toHaveBeenCalledTimes(2);
});
});
describe('getSearchWithMode', () => {
test('adds mode to an empty search string', () => {
expect(getSearchWithMode('', 'edgeless')).toBe('?mode=edgeless');
@@ -37,6 +37,10 @@ import { PageNotFound } from '../../404';
import { ShareFooter } from './share-footer';
import { ShareHeader } from './share-header';
import * as styles from './share-page.css';
import {
fetchSharedPublishMode,
getResolvedPublishMode,
} from './share-page.utils';
import { useSharedModeQuerySync } from './use-shared-mode-query-sync';
const useUpdateBasename = (workspace: Workspace | null) => {
@@ -127,19 +131,58 @@ const SharePageInner = ({
const [page, setPage] = useState<Doc | null>(null);
const [editor, setEditor] = useState<Editor | null>(null);
const [noPermission, setNoPermission] = useState(false);
const [fetchedPublishMode, setFetchedPublishMode] = useState<
DocMode | null | undefined
>(() => (publishMode === undefined ? undefined : null));
const [editorContainer, setActiveBlocksuiteEditor] =
useActiveBlocksuiteEditor();
const resolvedPublishMode = publishMode ?? null;
const resolvedPublishMode =
publishMode !== undefined
? publishMode
: fetchedPublishMode === undefined
? null
: getResolvedPublishMode(null, fetchedPublishMode);
const currentPublishMode = useSharedModeQuerySync({
editor,
resolvedPublishMode,
});
useEffect(() => {
if (editor || workspace || page) {
if (publishMode !== undefined) {
setFetchedPublishMode(null);
return;
}
const abortController = new AbortController();
setFetchedPublishMode(undefined);
void fetchSharedPublishMode({
serverBaseUrl: serverService.server.baseUrl,
workspaceId,
docId,
signal: abortController.signal,
})
.then(mode => {
if (!abortController.signal.aborted) {
setFetchedPublishMode(mode);
}
})
.catch(err => {
if (!abortController.signal.aborted) {
console.error(err);
setFetchedPublishMode(null);
}
});
return () => {
abortController.abort();
};
}, [docId, publishMode, serverService.server.baseUrl, workspaceId]);
useEffect(() => {
if (resolvedPublishMode === null) return;
if (editor || workspace || page) return;
// create a workspace for share page
const { workspace: sharedWorkspace } = workspacesService.open(
{
@@ -188,7 +231,7 @@ const SharePageInner = ({
setPage(doc);
const editor = doc.scope.get(EditorsService).createEditor();
editor.setMode(resolvedPublishMode ?? doc.getPrimaryMode() ?? 'page');
editor.setMode(resolvedPublishMode);
if (selector) {
editor.setSelector(selector);
@@ -1,15 +1,67 @@
import { PublicDocMode } from '@affine/graphql';
import { type DocMode, DocModes } from '@blocksuite/affine/model';
export const getResolvedPublishMode = (
queryMode: DocMode | null,
publicMode?: PublicDocMode | null
publishMode?: DocMode | null
): DocMode => {
if (queryMode && DocModes.includes(queryMode)) {
return queryMode;
}
return publicMode === PublicDocMode.Edgeless ? 'edgeless' : 'page';
return publishMode === 'edgeless' ? 'edgeless' : 'page';
};
export const parsePublishMode = (
publishMode: string | null | undefined
): DocMode | null => {
if (!publishMode) {
return null;
}
return DocModes.includes(publishMode as DocMode)
? (publishMode as DocMode)
: null;
};
export const fetchSharedPublishMode = async ({
serverBaseUrl,
workspaceId,
docId,
signal,
}: {
serverBaseUrl: string;
workspaceId: string;
docId: string;
signal?: AbortSignal;
}): Promise<DocMode | null> => {
const url = new URL(
`/api/workspaces/${encodeURIComponent(workspaceId)}/docs/${encodeURIComponent(docId)}`,
serverBaseUrl
);
const headers = {
Accept: 'application/octet-stream',
'x-affine-version': BUILD_CONFIG.appVersion,
};
const headResponse = await globalThis.fetch(url, {
method: 'HEAD',
headers,
signal,
});
const headMode = parsePublishMode(headResponse.headers.get('publish-mode'));
if (headMode || headResponse.status === 404 || !headResponse.ok) {
return headMode;
}
const getResponse = await globalThis.fetch(url, {
headers,
signal,
});
try {
return parsePublishMode(getResponse.headers.get('publish-mode'));
} finally {
await getResponse.body?.cancel();
}
};
export const getSearchWithMode = (search: string, mode: DocMode) => {
+5 -4
View File
@@ -200,13 +200,14 @@ test('share page with default edgeless', async ({ page, browser }) => {
{
const context = await browser.newContext();
await skipOnboarding(context);
const url: string = await page.evaluate(() =>
navigator.clipboard.readText()
const url = new URL(
await page.evaluate(() => navigator.clipboard.readText())
);
url.searchParams.delete('mode');
const page2 = await context.newPage();
await page2.goto(url);
await page2.goto(url.toString());
await waitForEditorLoad(page2);
await expect(page.locator('affine-edgeless-root')).toBeVisible({
await expect(page2.locator('affine-edgeless-root')).toBeVisible({
timeout: 1000,
});
await expect(page2.locator('affine-paragraph').first()).toContainText(