mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-19 07:17:00 +08:00
feat: support page sharing by meta (#1858)
This commit is contained in:
3
apps/web/src/hooks/affine/use-affine-public-page.ts
Normal file
3
apps/web/src/hooks/affine/use-affine-public-page.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import type { AffineWorkspace } from '@affine/workspace/type';
|
||||||
|
|
||||||
|
export function useAffinePublicPage(workspace: AffineWorkspace) {}
|
||||||
@@ -7,15 +7,17 @@ import { readFile } from 'node:fs/promises';
|
|||||||
|
|
||||||
import { MessageCode } from '@affine/env/constant';
|
import { MessageCode } from '@affine/env/constant';
|
||||||
import { createStatusApis } from '@affine/workspace/affine/api/status';
|
import { createStatusApis } from '@affine/workspace/affine/api/status';
|
||||||
import type { KeckProvider } from '@affine/workspace/affine/keck';
|
import { KeckProvider } from '@affine/workspace/affine/keck';
|
||||||
|
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||||
import user1 from '@affine-test/fixtures/built-in-user1.json';
|
import user1 from '@affine-test/fixtures/built-in-user1.json';
|
||||||
import user2 from '@affine-test/fixtures/built-in-user2.json';
|
import user2 from '@affine-test/fixtures/built-in-user2.json';
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page, PageMeta } from '@blocksuite/store';
|
||||||
import { Workspace } from '@blocksuite/store';
|
import { Workspace } from '@blocksuite/store';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
import { WebSocket } from 'ws';
|
||||||
import { applyUpdate } from 'yjs';
|
import { applyUpdate } from 'yjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -32,6 +34,9 @@ import {
|
|||||||
setLoginStorage,
|
setLoginStorage,
|
||||||
} from '../login';
|
} from '../login';
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
globalThis.WebSocket = WebSocket;
|
||||||
|
|
||||||
let workspaceApis: ReturnType<typeof createWorkspaceApis>;
|
let workspaceApis: ReturnType<typeof createWorkspaceApis>;
|
||||||
let userApis: ReturnType<typeof createUserApis>;
|
let userApis: ReturnType<typeof createUserApis>;
|
||||||
let affineAuth: ReturnType<typeof createAffineAuth>;
|
let affineAuth: ReturnType<typeof createAffineAuth>;
|
||||||
@@ -58,6 +63,15 @@ const mockUser = {
|
|||||||
password: faker.internet.password(),
|
password: faker.internet.password(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function waitForConnected(provider: KeckProvider) {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
provider.once('status', ({ status }: any) => {
|
||||||
|
expect(status).toBe('connected');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// create a new user for each test, so that each test can be run independently
|
// create a new user for each test, so that each test can be run independently
|
||||||
mockUser.name = faker.name.fullName();
|
mockUser.name = faker.name.fullName();
|
||||||
@@ -99,30 +113,19 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const wsUrl = `ws://127.0.0.1:3000/api/sync/`;
|
|
||||||
|
|
||||||
async function createWorkspace(
|
async function createWorkspace(
|
||||||
workspaceApi: typeof workspaceApis,
|
workspaceApi: typeof workspaceApis,
|
||||||
callback?: (workspace: Workspace) => void
|
callback?: (workspace: Workspace) => void
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const workspace = new Workspace({
|
const workspace = createEmptyBlockSuiteWorkspace(
|
||||||
id: faker.datatype.uuid(),
|
faker.datatype.uuid(),
|
||||||
})
|
_ => undefined
|
||||||
.register(AffineSchemas)
|
);
|
||||||
.register(__unstableSchemas);
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(workspace);
|
callback(workspace);
|
||||||
}
|
}
|
||||||
const binary = Workspace.Y.encodeStateAsUpdate(workspace.doc);
|
const binary = Workspace.Y.encodeStateAsUpdate(workspace.doc);
|
||||||
const data = await workspaceApi.createWorkspace(binary);
|
const data = await workspaceApi.createWorkspace(binary);
|
||||||
function waitForConnected(provider: KeckProvider) {
|
|
||||||
return new Promise<void>(resolve => {
|
|
||||||
provider.once('status', ({ status }: any) => {
|
|
||||||
expect(status).toBe('connected');
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
createWorkspaceResponseSchema.parse(data);
|
createWorkspaceResponseSchema.parse(data);
|
||||||
return data.id;
|
return data.id;
|
||||||
}
|
}
|
||||||
@@ -389,4 +392,82 @@ describe('api', () => {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'public page',
|
||||||
|
async () => {
|
||||||
|
const id = await createWorkspace(workspaceApis, workspace => {
|
||||||
|
const page = workspace.createPage('page0');
|
||||||
|
const { frameId } = initPage(page);
|
||||||
|
page.addBlock(
|
||||||
|
'affine:paragraph',
|
||||||
|
{
|
||||||
|
text: new page.Text('This is page0'),
|
||||||
|
},
|
||||||
|
frameId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const binary = await workspaceApis.downloadWorkspace(id, false);
|
||||||
|
const workspace = createEmptyBlockSuiteWorkspace(id, () => undefined);
|
||||||
|
Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(binary));
|
||||||
|
const workspace2 = createEmptyBlockSuiteWorkspace(id, () => undefined);
|
||||||
|
{
|
||||||
|
const wsUrl = `ws://127.0.0.1:3000/api/sync/`;
|
||||||
|
const provider = new KeckProvider(wsUrl, workspace.id, workspace.doc, {
|
||||||
|
params: { token: getLoginStorage()?.token },
|
||||||
|
// @ts-expect-error ignore the type
|
||||||
|
awareness: workspace.awarenessStore.awareness,
|
||||||
|
connect: false,
|
||||||
|
});
|
||||||
|
const provider2 = new KeckProvider(
|
||||||
|
wsUrl,
|
||||||
|
workspace2.id,
|
||||||
|
workspace2.doc,
|
||||||
|
{
|
||||||
|
params: { token: getLoginStorage()?.token },
|
||||||
|
// @ts-expect-error ignore the type
|
||||||
|
awareness: workspace2.awarenessStore.awareness,
|
||||||
|
connect: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
provider.connect();
|
||||||
|
provider2.connect();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
await waitForConnected(provider),
|
||||||
|
await waitForConnected(provider2),
|
||||||
|
]);
|
||||||
|
const pageId = 'page0';
|
||||||
|
const page = workspace.getPage(pageId) as Page;
|
||||||
|
expect(page).not.toBeNull();
|
||||||
|
expect(page).not.toBeUndefined();
|
||||||
|
workspace.setPageMeta(pageId, {
|
||||||
|
isPublic: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
const page2 = workspace2.getPage(pageId) as Page;
|
||||||
|
expect(page2).not.toBeNull();
|
||||||
|
const meta = workspace2.meta.getPageMeta(pageId) as PageMeta;
|
||||||
|
expect(meta.isPublic).toBe(true);
|
||||||
|
|
||||||
|
const binary = await workspaceApis.downloadPublicWorkspacePage(
|
||||||
|
id,
|
||||||
|
pageId
|
||||||
|
);
|
||||||
|
const publicWorkspace = createEmptyBlockSuiteWorkspace(
|
||||||
|
id,
|
||||||
|
() => undefined
|
||||||
|
);
|
||||||
|
Workspace.Y.applyUpdate(publicWorkspace.doc, new Uint8Array(binary));
|
||||||
|
const publicPage = publicWorkspace.getPage(pageId) as Page;
|
||||||
|
expect(publicPage).not.toBeNull();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 30000,
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -141,7 +141,13 @@ describe('ydoc sync', () => {
|
|||||||
text: new page1.Text('hello world'),
|
text: new page1.Text('hello world'),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
workspace1.meta.setPageMeta(pageId, {
|
||||||
|
foo: 'bar',
|
||||||
|
});
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
const pageMeta = workspace2.meta.getPageMeta(pageId);
|
||||||
|
expect(pageMeta).toBeDefined();
|
||||||
|
expect(pageMeta?.foo).toBe('bar');
|
||||||
const paragraph2 = page2.getBlockById(paragraphId) as ParagraphBlockModel;
|
const paragraph2 = page2.getBlockById(paragraphId) as ParagraphBlockModel;
|
||||||
const text = paragraph2.text as Text;
|
const text = paragraph2.text as Text;
|
||||||
expect(text.toString()).toEqual(
|
expect(text.toString()).toEqual(
|
||||||
|
|||||||
@@ -380,7 +380,9 @@ export function createWorkspaceApis(prefixUrl = '/') {
|
|||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}
|
}
|
||||||
).then(r => r.arrayBuffer());
|
).then(r =>
|
||||||
|
r.ok ? r.arrayBuffer() : Promise.reject(new Error(`${r.status}`))
|
||||||
|
);
|
||||||
},
|
},
|
||||||
downloadWorkspace: async (
|
downloadWorkspace: async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user