feat: follow publish mode if no params
This commit is contained in:
@@ -1,24 +1,21 @@
|
|||||||
import { PublicDocMode } from '@affine/graphql';
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||||
import { describe, expect, test } from 'vitest';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
fetchSharedPublishMode,
|
||||||
getResolvedPublishMode,
|
getResolvedPublishMode,
|
||||||
getSearchWithMode,
|
getSearchWithMode,
|
||||||
|
parsePublishMode,
|
||||||
} from '../desktop/pages/workspace/share/share-page.utils';
|
} from '../desktop/pages/workspace/share/share-page.utils';
|
||||||
|
|
||||||
describe('getResolvedPublishMode', () => {
|
describe('getResolvedPublishMode', () => {
|
||||||
test('prefers the query mode when it is present', () => {
|
test('prefers the query mode when it is present', () => {
|
||||||
expect(getResolvedPublishMode('edgeless', PublicDocMode.Page)).toBe(
|
expect(getResolvedPublishMode('edgeless', 'page')).toBe('edgeless');
|
||||||
'edgeless'
|
expect(getResolvedPublishMode('page', 'edgeless')).toBe('page');
|
||||||
);
|
|
||||||
expect(getResolvedPublishMode('page', PublicDocMode.Edgeless)).toBe('page');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('falls back to the published public mode for shared docs', () => {
|
test('falls back to the published public mode for shared docs', () => {
|
||||||
expect(getResolvedPublishMode(null, PublicDocMode.Edgeless)).toBe(
|
expect(getResolvedPublishMode(null, 'edgeless')).toBe('edgeless');
|
||||||
'edgeless'
|
expect(getResolvedPublishMode(null, 'page')).toBe('page');
|
||||||
);
|
|
||||||
expect(getResolvedPublishMode(null, PublicDocMode.Page)).toBe('page');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('defaults to page when no mode is available', () => {
|
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', () => {
|
describe('getSearchWithMode', () => {
|
||||||
test('adds mode to an empty search string', () => {
|
test('adds mode to an empty search string', () => {
|
||||||
expect(getSearchWithMode('', 'edgeless')).toBe('?mode=edgeless');
|
expect(getSearchWithMode('', 'edgeless')).toBe('?mode=edgeless');
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ import { PageNotFound } from '../../404';
|
|||||||
import { ShareFooter } from './share-footer';
|
import { ShareFooter } from './share-footer';
|
||||||
import { ShareHeader } from './share-header';
|
import { ShareHeader } from './share-header';
|
||||||
import * as styles from './share-page.css';
|
import * as styles from './share-page.css';
|
||||||
|
import {
|
||||||
|
fetchSharedPublishMode,
|
||||||
|
getResolvedPublishMode,
|
||||||
|
} from './share-page.utils';
|
||||||
import { useSharedModeQuerySync } from './use-shared-mode-query-sync';
|
import { useSharedModeQuerySync } from './use-shared-mode-query-sync';
|
||||||
|
|
||||||
const useUpdateBasename = (workspace: Workspace | null) => {
|
const useUpdateBasename = (workspace: Workspace | null) => {
|
||||||
@@ -127,19 +131,58 @@ const SharePageInner = ({
|
|||||||
const [page, setPage] = useState<Doc | null>(null);
|
const [page, setPage] = useState<Doc | null>(null);
|
||||||
const [editor, setEditor] = useState<Editor | null>(null);
|
const [editor, setEditor] = useState<Editor | null>(null);
|
||||||
const [noPermission, setNoPermission] = useState(false);
|
const [noPermission, setNoPermission] = useState(false);
|
||||||
|
const [fetchedPublishMode, setFetchedPublishMode] = useState<
|
||||||
|
DocMode | null | undefined
|
||||||
|
>(() => (publishMode === undefined ? undefined : null));
|
||||||
const [editorContainer, setActiveBlocksuiteEditor] =
|
const [editorContainer, setActiveBlocksuiteEditor] =
|
||||||
useActiveBlocksuiteEditor();
|
useActiveBlocksuiteEditor();
|
||||||
const resolvedPublishMode = publishMode ?? null;
|
const resolvedPublishMode =
|
||||||
|
publishMode !== undefined
|
||||||
|
? publishMode
|
||||||
|
: fetchedPublishMode === undefined
|
||||||
|
? null
|
||||||
|
: getResolvedPublishMode(null, fetchedPublishMode);
|
||||||
const currentPublishMode = useSharedModeQuerySync({
|
const currentPublishMode = useSharedModeQuerySync({
|
||||||
editor,
|
editor,
|
||||||
resolvedPublishMode,
|
resolvedPublishMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editor || workspace || page) {
|
if (publishMode !== undefined) {
|
||||||
|
setFetchedPublishMode(null);
|
||||||
return;
|
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
|
// create a workspace for share page
|
||||||
const { workspace: sharedWorkspace } = workspacesService.open(
|
const { workspace: sharedWorkspace } = workspacesService.open(
|
||||||
{
|
{
|
||||||
@@ -188,7 +231,7 @@ const SharePageInner = ({
|
|||||||
setPage(doc);
|
setPage(doc);
|
||||||
|
|
||||||
const editor = doc.scope.get(EditorsService).createEditor();
|
const editor = doc.scope.get(EditorsService).createEditor();
|
||||||
editor.setMode(resolvedPublishMode ?? doc.getPrimaryMode() ?? 'page');
|
editor.setMode(resolvedPublishMode);
|
||||||
|
|
||||||
if (selector) {
|
if (selector) {
|
||||||
editor.setSelector(selector);
|
editor.setSelector(selector);
|
||||||
|
|||||||
@@ -1,15 +1,67 @@
|
|||||||
import { PublicDocMode } from '@affine/graphql';
|
|
||||||
import { type DocMode, DocModes } from '@blocksuite/affine/model';
|
import { type DocMode, DocModes } from '@blocksuite/affine/model';
|
||||||
|
|
||||||
export const getResolvedPublishMode = (
|
export const getResolvedPublishMode = (
|
||||||
queryMode: DocMode | null,
|
queryMode: DocMode | null,
|
||||||
publicMode?: PublicDocMode | null
|
publishMode?: DocMode | null
|
||||||
): DocMode => {
|
): DocMode => {
|
||||||
if (queryMode && DocModes.includes(queryMode)) {
|
if (queryMode && DocModes.includes(queryMode)) {
|
||||||
return 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) => {
|
export const getSearchWithMode = (search: string, mode: DocMode) => {
|
||||||
|
|||||||
@@ -200,13 +200,14 @@ test('share page with default edgeless', async ({ page, browser }) => {
|
|||||||
{
|
{
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
await skipOnboarding(context);
|
await skipOnboarding(context);
|
||||||
const url: string = await page.evaluate(() =>
|
const url = new URL(
|
||||||
navigator.clipboard.readText()
|
await page.evaluate(() => navigator.clipboard.readText())
|
||||||
);
|
);
|
||||||
|
url.searchParams.delete('mode');
|
||||||
const page2 = await context.newPage();
|
const page2 = await context.newPage();
|
||||||
await page2.goto(url);
|
await page2.goto(url.toString());
|
||||||
await waitForEditorLoad(page2);
|
await waitForEditorLoad(page2);
|
||||||
await expect(page.locator('affine-edgeless-root')).toBeVisible({
|
await expect(page2.locator('affine-edgeless-root')).toBeVisible({
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
});
|
});
|
||||||
await expect(page2.locator('affine-paragraph').first()).toContainText(
|
await expect(page2.locator('affine-paragraph').first()).toContainText(
|
||||||
|
|||||||
Reference in New Issue
Block a user