diff --git a/packages/backend/server/migrations/20240826033024_editor_record/migration.sql b/packages/backend/server/migrations/20240826033024_editor_record/migration.sql new file mode 100644 index 000000000000..13b0e3161737 --- /dev/null +++ b/packages/backend/server/migrations/20240826033024_editor_record/migration.sql @@ -0,0 +1,21 @@ +-- AlterTable +ALTER TABLE "snapshot_histories" ADD COLUMN "created_by" VARCHAR; + +-- AlterTable +ALTER TABLE "snapshots" ADD COLUMN "created_by" VARCHAR, +ADD COLUMN "updated_by" VARCHAR; + +-- AlterTable +ALTER TABLE "updates" ADD COLUMN "created_by" VARCHAR DEFAULT 'system'; + +-- AddForeignKey +ALTER TABLE "snapshots" ADD CONSTRAINT "snapshots_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "snapshots" ADD CONSTRAINT "snapshots_updated_by_fkey" FOREIGN KEY ("updated_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "updates" ADD CONSTRAINT "updates_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "snapshot_histories" ADD CONSTRAINT "snapshot_histories_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/backend/server/schema.prisma b/packages/backend/server/schema.prisma index 09ff8556907b..eb91a3b98744 100644 --- a/packages/backend/server/schema.prisma +++ b/packages/backend/server/schema.prisma @@ -33,6 +33,10 @@ model User { aiSessions AiSession[] updatedRuntimeConfigs RuntimeConfig[] userSnapshots UserSnapshot[] + createdSnapshot Snapshot[] @relation("createdSnapshot") + updatedSnapshot Snapshot[] @relation("updatedSnapshot") + createdUpdate Update[] @relation("createdUpdate") + createdHistory SnapshotHistory[] @relation("createdHistory") @@index([email]) @@map("users") @@ -241,9 +245,16 @@ model Snapshot { // the `updated_at` field will not record the time of record changed, // but the created time of last seen update that has been merged into snapshot. updatedAt DateTime @map("updated_at") @db.Timestamptz(3) + createdBy String? @map("created_by") @db.VarChar + updatedBy String? @map("updated_by") @db.VarChar + + // should not delete origin snapshot even if user is deleted + // we only delete the snapshot if the workspace is deleted + createdByUser User? @relation(name: "createdSnapshot", fields: [createdBy], references: [id], onDelete: SetNull) + updatedByUser User? @relation(name: "updatedSnapshot", fields: [updatedBy], references: [id], onDelete: SetNull) // @deprecated use updatedAt only - seq Int? @default(0) @db.Integer + seq Int? @default(0) @db.Integer // we need to clear all hanging updates and snapshots before enable the foreign key on workspaceId // workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) @@ -274,9 +285,14 @@ model Update { id String @map("guid") @db.VarChar blob Bytes @db.ByteA createdAt DateTime @map("created_at") @db.Timestamptz(3) + // TODO(@darkskygit): fullfill old update, remove default value in next release + createdBy String? @default("system") @map("created_by") @db.VarChar + + // will delete createor record if createor's account is deleted + createdByUser User? @relation(name: "createdUpdate", fields: [createdBy], references: [id], onDelete: SetNull) // @deprecated use createdAt only - seq Int? @db.Integer + seq Int? @db.Integer @@id([workspaceId, id, createdAt]) @@map("updates") @@ -289,6 +305,10 @@ model SnapshotHistory { blob Bytes @db.ByteA state Bytes? @db.ByteA expiredAt DateTime @map("expired_at") @db.Timestamptz(3) + createdBy String? @map("created_by") @db.VarChar + + // will delete createor record if creator's account is deleted + createdByUser User? @relation(name: "createdHistory", fields: [createdBy], references: [id], onDelete: SetNull) @@id([workspaceId, id, timestamp]) @@map("snapshot_histories") diff --git a/packages/backend/server/src/core/doc/adapters/userspace.ts b/packages/backend/server/src/core/doc/adapters/userspace.ts index b5fd9a2655a3..99b9289e2530 100644 --- a/packages/backend/server/src/core/doc/adapters/userspace.ts +++ b/packages/backend/server/src/core/doc/adapters/userspace.ts @@ -45,7 +45,12 @@ export class PgUserspaceDocStorageAdapter extends DocStorageAdapter { return this.getDocSnapshot(spaceId, docId); } - async pushDocUpdates(userId: string, docId: string, updates: Uint8Array[]) { + async pushDocUpdates( + userId: string, + docId: string, + updates: Uint8Array[], + editorId?: string + ) { if (!updates.length) { return 0; } @@ -67,6 +72,7 @@ export class PgUserspaceDocStorageAdapter extends DocStorageAdapter { docId, bin, timestamp, + editor: editorId, }); return timestamp; @@ -135,6 +141,7 @@ export class PgUserspaceDocStorageAdapter extends DocStorageAdapter { docId, bin: snapshot.blob, timestamp: snapshot.updatedAt.getTime(), + editor: snapshot.userId, }; } diff --git a/packages/backend/server/src/core/doc/adapters/workspace.ts b/packages/backend/server/src/core/doc/adapters/workspace.ts index 2325595c0809..710e07ac0c7e 100644 --- a/packages/backend/server/src/core/doc/adapters/workspace.ts +++ b/packages/backend/server/src/core/doc/adapters/workspace.ts @@ -38,7 +38,8 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { async pushDocUpdates( workspaceId: string, docId: string, - updates: Uint8Array[] + updates: Uint8Array[], + editorId?: string ) { if (!updates.length) { return 0; @@ -82,6 +83,7 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { blob: Buffer.from(update), seq, createdAt: new Date(createdAt), + createdBy: editorId || null, }; }), }); @@ -113,6 +115,7 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { return rows.map(row => ({ bin: row.blob, timestamp: row.createdAt.getTime(), + editor: row.createdBy || undefined, })); } @@ -216,6 +219,12 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { const histories = await this.db.snapshotHistory.findMany({ select: { timestamp: true, + createdByUser: { + select: { + name: true, + avatarUrl: true, + }, + }, }, where: { workspaceId, @@ -230,7 +239,10 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { take: query.limit, }); - return histories.map(h => h.timestamp.getTime()); + return histories.map(h => ({ + timestamp: h.timestamp.getTime(), + editor: h.createdByUser, + })); } async getDocHistory(workspaceId: string, docId: string, timestamp: number) { @@ -253,13 +265,15 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { docId, bin: history.blob, timestamp, + editor: history.createdBy || undefined, }; } override async rollbackDoc( spaceId: string, docId: string, - timestamp: number + timestamp: number, + editorId?: string ): Promise { await using _lock = await this.lockDocForUpdate(spaceId, docId); const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp); @@ -274,7 +288,14 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { } // force create a new history record after rollback - await this.createDocHistory(fromSnapshot, true); + await this.createDocHistory( + { + ...fromSnapshot, + // override the editor to the one who requested the rollback + editor: editorId, + }, + true + ); // WARN: // we should never do the snapshot updating in recovering, // which is not the solution in CRDT. @@ -331,6 +352,7 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { id: snapshot.docId, timestamp: new Date(snapshot.timestamp), blob: Buffer.from(snapshot.bin), + createdBy: snapshot.editor, expiredAt: new Date( Date.now() + (await this.options.historyMaxAge(snapshot.spaceId)) ), @@ -374,6 +396,8 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { docId, bin: snapshot.blob, timestamp: snapshot.updatedAt.getTime(), + // creator and editor may null if their account is deleted + editor: snapshot.updatedBy || snapshot.createdBy || undefined, }; } @@ -396,10 +420,10 @@ export class PgWorkspaceDocStorageAdapter extends DocStorageAdapter { // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ try { const result: { updatedAt: Date }[] = await this.db.$queryRaw` - INSERT INTO "snapshots" ("workspace_id", "guid", "blob", "created_at", "updated_at") - VALUES (${spaceId}, ${docId}, ${bin}, DEFAULT, ${updatedAt}) + INSERT INTO "snapshots" ("workspace_id", "guid", "blob", "created_at", "updated_at", "created_by", "updated_by") + VALUES (${spaceId}, ${docId}, ${bin}, DEFAULT, ${updatedAt}, ${snapshot.editor}, ${snapshot.editor}) ON CONFLICT ("workspace_id", "guid") - DO UPDATE SET "blob" = ${bin}, "updated_at" = ${updatedAt} + DO UPDATE SET "blob" = ${bin}, "updated_at" = ${updatedAt}, "updated_by" = ${snapshot.editor} WHERE "snapshots"."workspace_id" = ${spaceId} AND "snapshots"."guid" = ${docId} AND "snapshots"."updated_at" <= ${updatedAt} RETURNING "snapshots"."workspace_id" as "workspaceId", "snapshots"."guid" as "id", "snapshots"."updated_at" as "updatedAt" `; diff --git a/packages/backend/server/src/core/doc/index.ts b/packages/backend/server/src/core/doc/index.ts index 55698352cae3..b0b7cb226e2f 100644 --- a/packages/backend/server/src/core/doc/index.ts +++ b/packages/backend/server/src/core/doc/index.ts @@ -22,4 +22,4 @@ import { DocStorageOptions } from './options'; export class DocStorageModule {} export { PgUserspaceDocStorageAdapter, PgWorkspaceDocStorageAdapter }; -export { DocStorageAdapter } from './storage'; +export { DocStorageAdapter, type Editor } from './storage'; diff --git a/packages/backend/server/src/core/doc/storage/doc.ts b/packages/backend/server/src/core/doc/storage/doc.ts index 1e5dd8cb02b5..999a9226fb3f 100644 --- a/packages/backend/server/src/core/doc/storage/doc.ts +++ b/packages/backend/server/src/core/doc/storage/doc.ts @@ -16,11 +16,13 @@ export interface DocRecord { docId: string; bin: Uint8Array; timestamp: number; + editor?: string; } export interface DocUpdate { bin: Uint8Array; timestamp: number; + editor?: string; } export interface HistoryFilter { @@ -28,6 +30,11 @@ export interface HistoryFilter { limit?: number; } +export interface Editor { + name: string; + avatarUrl: string | null; +} + export interface DocStorageOptions { mergeUpdates?: (updates: Uint8Array[]) => Promise | Uint8Array; } @@ -61,7 +68,7 @@ export abstract class DocStorageAdapter extends Connection { const updates = await this.getDocUpdates(spaceId, docId); if (updates.length) { - const { timestamp, bin } = await this.squash( + const { timestamp, bin, editor } = await this.squash( snapshot ? [snapshot, ...updates] : updates ); @@ -70,6 +77,7 @@ export abstract class DocStorageAdapter extends Connection { docId, bin, timestamp, + editor, }; const success = await this.setDocSnapshot(newSnapshot); @@ -91,7 +99,8 @@ export abstract class DocStorageAdapter extends Connection { abstract pushDocUpdates( spaceId: string, docId: string, - updates: Uint8Array[] + updates: Uint8Array[], + editorId?: string ): Promise; abstract deleteDoc(spaceId: string, docId: string): Promise; @@ -99,7 +108,8 @@ export abstract class DocStorageAdapter extends Connection { async rollbackDoc( spaceId: string, docId: string, - timestamp: number + timestamp: number, + editorId?: string ): Promise { await using _lock = await this.lockDocForUpdate(spaceId, docId); const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp); @@ -114,7 +124,7 @@ export abstract class DocStorageAdapter extends Connection { } const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin); - await this.pushDocUpdates(spaceId, docId, [change]); + await this.pushDocUpdates(spaceId, docId, [change], editorId); // force create a new history record after rollback await this.createDocHistory(fromSnapshot, true); } @@ -127,7 +137,7 @@ export abstract class DocStorageAdapter extends Connection { spaceId: string, docId: string, query: { skip?: number; limit?: number } - ): Promise; + ): Promise<{ timestamp: number; editor: Editor | null }[]>; abstract getDocHistory( spaceId: string, docId: string, @@ -173,6 +183,7 @@ export abstract class DocStorageAdapter extends Connection { return { bin: finalUpdate, timestamp: lastUpdate.timestamp, + editor: lastUpdate.editor, }; } diff --git a/packages/backend/server/src/core/doc/storage/index.ts b/packages/backend/server/src/core/doc/storage/index.ts index a69fc46d9204..6ba0e23dd111 100644 --- a/packages/backend/server/src/core/doc/storage/index.ts +++ b/packages/backend/server/src/core/doc/storage/index.ts @@ -28,5 +28,6 @@ export { DocStorageAdapter, type DocStorageOptions, type DocUpdate, + type Editor, type HistoryFilter, } from './doc'; diff --git a/packages/backend/server/src/core/sync/gateway.ts b/packages/backend/server/src/core/sync/gateway.ts index b026c430ef6c..0c7a815b81bc 100644 --- a/packages/backend/server/src/core/sync/gateway.ts +++ b/packages/backend/server/src/core/sync/gateway.ts @@ -264,9 +264,11 @@ export class SpaceSyncGateway }; } + @Auth() @SubscribeMessage('space:push-doc-updates') async onReceiveDocUpdates( @ConnectedSocket() client: Socket, + @CurrentUser() user: CurrentUser, @MessageBody() message: PushDocUpdatesMessage ): Promise> { @@ -277,7 +279,8 @@ export class SpaceSyncGateway const timestamp = await adapter.push( spaceId, docId, - updates.map(update => Buffer.from(update, 'base64')) + updates.map(update => Buffer.from(update, 'base64')), + user.id ); // could be put in [adapter.push] @@ -448,8 +451,10 @@ export class SpaceSyncGateway }); } + @Auth() @SubscribeMessage('client-update-v2') async handleClientUpdateV2( + @CurrentUser() user: CurrentUser, @MessageBody() { workspaceId, @@ -462,7 +467,7 @@ export class SpaceSyncGateway }, @ConnectedSocket() client: Socket ): Promise> { - return this.onReceiveDocUpdates(client, { + return this.onReceiveDocUpdates(client, user, { spaceType: SpaceType.Workspace, spaceId: workspaceId, docId: guid, @@ -596,9 +601,9 @@ abstract class SyncSocketAdapter { permission?: Permission ): Promise; - push(spaceId: string, docId: string, updates: Buffer[]) { + push(spaceId: string, docId: string, updates: Buffer[], editorId: string) { this.assertIn(spaceId); - return this.storage.pushDocUpdates(spaceId, docId, updates); + return this.storage.pushDocUpdates(spaceId, docId, updates, editorId); } get(spaceId: string, docId: string) { @@ -621,9 +626,14 @@ class WorkspaceSyncAdapter extends SyncSocketAdapter { super(SpaceType.Workspace, client, storage); } - override push(spaceId: string, docId: string, updates: Buffer[]) { + override push( + spaceId: string, + docId: string, + updates: Buffer[], + editorId: string + ) { const id = new DocID(docId, spaceId); - return super.push(spaceId, id.guid, updates); + return super.push(spaceId, id.guid, updates, editorId); } override get(spaceId: string, docId: string) { diff --git a/packages/backend/server/src/core/workspaces/resolvers/history.ts b/packages/backend/server/src/core/workspaces/resolvers/history.ts index d11d2b6f03b4..8812d6bb3ca8 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/history.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/history.ts @@ -16,6 +16,7 @@ import { PgWorkspaceDocStorageAdapter } from '../../doc'; import { Permission, PermissionService } from '../../permission'; import { DocID } from '../../utils/doc'; import { WorkspaceType } from '../types'; +import { EditorType } from './workspace'; @ObjectType() class DocHistoryType implements Partial { @@ -27,6 +28,9 @@ class DocHistoryType implements Partial { @Field(() => GraphQLISODateTime) timestamp!: Date; + + @Field(() => EditorType, { nullable: true }) + editor!: EditorType | null; } @Resolver(() => WorkspaceType) @@ -47,17 +51,18 @@ export class DocHistoryResolver { ): Promise { const docId = new DocID(guid, workspace.id); - const timestamps = await this.workspace.listDocHistories( + const histories = await this.workspace.listDocHistories( workspace.id, docId.guid, { before: timestamp.getTime(), limit: take } ); - return timestamps.map(timestamp => { + return histories.map(history => { return { workspaceId: workspace.id, id: docId.guid, - timestamp: new Date(timestamp), + timestamp: new Date(history.timestamp), + editor: history.editor, }; }); } @@ -81,7 +86,8 @@ export class DocHistoryResolver { await this.workspace.rollbackDoc( docId.workspace, docId.guid, - timestamp.getTime() + timestamp.getTime(), + user.id ); return timestamp; diff --git a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts index ea7ff150b7a1..16037f03f3b5 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/workspace.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/workspace.ts @@ -1,8 +1,10 @@ import { Logger } from '@nestjs/common'; import { Args, + Field, Int, Mutation, + ObjectType, Parent, Query, ResolveField, @@ -16,6 +18,7 @@ import { applyUpdate, Doc } from 'yjs'; import type { FileUpload } from '../../../fundamentals'; import { CantChangeSpaceOwner, + DocNotFound, EventEmitter, InternalServerError, MailService, @@ -28,6 +31,7 @@ import { UserNotFound, } from '../../../fundamentals'; import { CurrentUser, Public } from '../../auth'; +import type { Editor } from '../../doc'; import { Permission, PermissionService } from '../../permission'; import { QuotaManagementService, QuotaQueryType } from '../../quota'; import { WorkspaceBlobStorage } from '../../storage'; @@ -40,6 +44,30 @@ import { } from '../types'; import { defaultWorkspaceAvatar } from '../utils'; +@ObjectType() +export class EditorType implements Partial { + @Field() + name!: string; + + @Field(() => String, { nullable: true }) + avatarUrl!: string | null; +} + +@ObjectType() +class WorkspacePageMeta { + @Field(() => Date) + createdAt!: Date; + + @Field(() => Date) + updatedAt!: Date; + + @Field(() => EditorType, { nullable: true }) + createdBy!: EditorType | null; + + @Field(() => EditorType, { nullable: true }) + updatedBy!: EditorType | null; +} + /** * Workspace resolver * Public apis rate limit: 10 req/m @@ -155,6 +183,35 @@ export class WorkspaceResolver { })); } + @ResolveField(() => WorkspacePageMeta, { + description: 'Cloud page metadata of workspace', + complexity: 2, + }) + async pageMeta( + @Parent() workspace: WorkspaceType, + @Args('pageId') pageId: string + ) { + const metadata = await this.prisma.snapshot.findFirst({ + where: { workspaceId: workspace.id, id: pageId }, + select: { + createdAt: true, + updatedAt: true, + createdByUser: { select: { name: true, avatarUrl: true } }, + updatedByUser: { select: { name: true, avatarUrl: true } }, + }, + }); + if (!metadata) { + throw new DocNotFound({ spaceId: workspace.id, docId: pageId }); + } + + return { + createdAt: metadata.createdAt, + updatedAt: metadata.updatedAt, + createdBy: metadata.createdByUser || null, + updatedBy: metadata.updatedByUser || null, + }; + } + @ResolveField(() => QuotaQueryType, { name: 'quota', description: 'quota of workspace', diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index b97103c91a6b..c656686bfa4e 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -189,6 +189,7 @@ type DocHistoryNotFoundDataType { } type DocHistoryType { + editor: EditorType id: String! timestamp: DateTime! workspaceId: String! @@ -199,6 +200,11 @@ type DocNotFoundDataType { spaceId: String! } +type EditorType { + avatarUrl: String + name: String! +} + union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocAccessDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | InvalidHistoryTimestampDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | VersionRejectedDataType enum ErrorNames { @@ -875,6 +881,13 @@ type WorkspacePage { workspaceId: String! } +type WorkspacePageMeta { + createdAt: DateTime! + createdBy: EditorType + updatedAt: DateTime! + updatedBy: EditorType +} + type WorkspaceType { """Available features of workspace""" availableFeatures: [FeatureType!]! @@ -905,6 +918,9 @@ type WorkspaceType { """Owner of workspace""" owner: UserType! + """Cloud page metadata of workspace""" + pageMeta(pageId: String!): WorkspacePageMeta! + """Permission of current signed in user in workspace""" permission: Permission! diff --git a/packages/backend/server/tests/doc/history.spec.ts b/packages/backend/server/tests/doc/history.spec.ts index 4da253c25936..70350e08fcbf 100644 --- a/packages/backend/server/tests/doc/history.spec.ts +++ b/packages/backend/server/tests/doc/history.spec.ts @@ -48,6 +48,8 @@ const snapshot: Snapshot = { seq: 0, updatedAt: new Date(), createdAt: new Date(), + createdBy: null, + updatedBy: null, }; function getSnapshot(timestamp: number = Date.now()): DocRecord { diff --git a/packages/backend/server/tests/doc/workspace.spec.ts b/packages/backend/server/tests/doc/workspace.spec.ts index 34389f1ff5c9..879f45f9b144 100644 --- a/packages/backend/server/tests/doc/workspace.spec.ts +++ b/packages/backend/server/tests/doc/workspace.spec.ts @@ -177,6 +177,7 @@ test('should be able to merge updates as snapshot', async t => { blob: Buffer.from(update), seq: 1, createdAt: new Date(Date.now() + 1), + createdBy: null, }, ], }); @@ -199,6 +200,7 @@ test('should be able to merge updates as snapshot', async t => { blob: appendUpdate, seq: 2, createdAt: new Date(), + createdBy: null, }, }); diff --git a/packages/common/infra/src/modules/feature-flag/constant.ts b/packages/common/infra/src/modules/feature-flag/constant.ts index 2d23e0cdfa84..8ccb6c490d39 100644 --- a/packages/common/infra/src/modules/feature-flag/constant.ts +++ b/packages/common/infra/src/modules/feature-flag/constant.ts @@ -78,8 +78,10 @@ export const AFFINE_FLAGS = { category: 'affine', displayName: 'Split View', description: - 'The Split View feature in AFFiNE allows users to divide their workspace into multiple sections, enabling simultaneous viewing and editing of different documents.The Split View feature in AFFiNE allows users to divide their workspace into multiple sections, enabling simultaneous viewing and editing of different documents.', + 'The Split View feature enables you to divide your tab into multiple sections for simultaneous viewing and editing of different documents.', feedbackType: 'discord', + feedbackLink: + 'https://discord.com/channels/959027316334407691/1280009690004324405', configurable: isDesktopEnvironment, defaultState: isCanaryBuild, }, @@ -88,6 +90,9 @@ export const AFFINE_FLAGS = { displayName: 'Emoji Folder Icon', description: 'Once enabled, you can use an emoji as the folder icon. When the first character of the folder name is an emoji, it will be extracted and used as its icon.', + feedbackType: 'discord', + feedbackLink: + 'https://discord.com/channels/959027316334407691/1280014319865696351/1280014319865696351', configurable: true, defaultState: false, }, diff --git a/packages/common/infra/src/modules/feature-flag/types.ts b/packages/common/infra/src/modules/feature-flag/types.ts index 897df817ece0..c521df716c76 100644 --- a/packages/common/infra/src/modules/feature-flag/types.ts +++ b/packages/common/infra/src/modules/feature-flag/types.ts @@ -6,6 +6,7 @@ export type FlagInfo = { configurable?: boolean; defaultState?: boolean; // default to open and not controlled by user feedbackType?: FeedbackType; + feedbackLink?: string; } & ( | { category: 'affine'; diff --git a/packages/frontend/component/src/ui/slider/index.css.ts b/packages/frontend/component/src/ui/slider/index.css.ts index 70b7528035fe..32c995b129ac 100644 --- a/packages/frontend/component/src/ui/slider/index.css.ts +++ b/packages/frontend/component/src/ui/slider/index.css.ts @@ -1,5 +1,9 @@ import { cssVarV2 } from '@toeverything/theme/v2'; -import { style } from '@vanilla-extract/css'; +import { createVar, style } from '@vanilla-extract/css'; + +export const thumbSize = createVar(); + +export const root = style({}); export const trackStyle = style({ width: '100%', @@ -11,7 +15,8 @@ export const trackStyle = style({ cursor: 'pointer', }); export const fakeTrackStyle = style({ - width: '100%', + width: `calc(100% - ${thumbSize})`, + transform: `translateX(calc(${thumbSize} * 0.5))`, height: '1px', backgroundColor: cssVarV2('layer/insideBorder/border'), position: 'relative', @@ -29,8 +34,8 @@ export const filledTrackStyle = style({ }); export const thumbStyle = style({ - width: '14px', - height: '14px', + width: thumbSize, + height: thumbSize, backgroundColor: cssVarV2('icon/primary'), borderRadius: '50%', position: 'absolute', diff --git a/packages/frontend/component/src/ui/slider/slider.tsx b/packages/frontend/component/src/ui/slider/slider.tsx index cba5734ce5e3..4f53dcf9d0d6 100644 --- a/packages/frontend/component/src/ui/slider/slider.tsx +++ b/packages/frontend/component/src/ui/slider/slider.tsx @@ -1,10 +1,13 @@ import * as Sliders from '@radix-ui/react-slider'; +import { assignInlineVars } from '@vanilla-extract/dynamic'; +import { clamp } from 'lodash-es'; import { useRef } from 'react'; import * as styles from './index.css'; export interface SliderProps extends Sliders.SliderProps { width?: number; + thumbSize?: number; containerStyle?: React.CSSProperties; rootStyle?: React.CSSProperties; trackStyle?: React.CSSProperties; @@ -14,6 +17,46 @@ export interface SliderProps extends Sliders.SliderProps { nodes?: number[]; // The values where the nodes should be placed } +// migrate https://github.com/radix-ui/primitives/blob/660060a765634e9cc7bf4513f41e8dabc9824d74/packages/react/slider/src/Slider.tsx#L708 to align step markers with thumbs +function calcStepMarkOffset( + index: number, + maxIndex: number, + thumbSize: number +) { + const percent = convertValueToPercentage(index, 0, maxIndex); + const thumbInBoundsOffset = getThumbInBoundsOffset(thumbSize, percent, 1); + return `calc(${percent}% + ${thumbInBoundsOffset}px)`; + + function convertValueToPercentage(value: number, min: number, max: number) { + const maxSteps = max - min; + const percentPerStep = 100 / maxSteps; + const percentage = percentPerStep * (value - min); + return clamp(percentage, 0, 100); + } + + function getThumbInBoundsOffset( + width: number, + left: number, + direction: number + ) { + const halfWidth = width / 2; + const halfPercent = 50; + const offset = linearScale([0, halfPercent], [0, halfWidth]); + return (halfWidth - offset(left) * direction) * direction; + } + + function linearScale( + input: readonly [number, number], + output: readonly [number, number] + ) { + return (value: number) => { + if (input[0] === input[1] || output[0] === output[1]) return output[0]; + const ratio = (output[1] - output[0]) / (input[1] - input[0]); + return output[0] + ratio * (value - input[0]); + }; + } +} + export const Slider = ({ value, min = 0, @@ -27,18 +70,28 @@ export const Slider = ({ rangeStyle, thumbStyle, noteStyle, + thumbSize = 14, ...props }: SliderProps) => { const sliderRef = useRef(null); return ( -
+
@@ -56,7 +109,11 @@ export const Slider = ({ className={styles.nodeStyle} data-active={value && value[0] >= nodeValue} style={{ - left: `${((nodeValue - (min !== undefined ? min : 0)) / (max !== undefined ? max - (min !== undefined ? min : 0) : 1)) * 100}%`, + left: calcStepMarkOffset( + index, + nodes.length - 1, + thumbSize / 2 + ), transform: index === 0 ? 'translateY(-50%)' diff --git a/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx b/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx index ba192afa6fe2..9a394d1caaca 100644 --- a/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx @@ -89,6 +89,7 @@ export const iconNames = [ 'edgeless', 'journal', 'payment', + 'createdEdited', ] as const satisfies fromLibIconName[]; export type PagePropertyIcon = (typeof iconNames)[number]; @@ -109,6 +110,10 @@ export const getDefaultIconName = ( return 'checkBoxCheckLinear'; case 'number': return 'number'; + case 'createdBy': + return 'createdEdited'; + case 'updatedBy': + return 'createdEdited'; default: return 'text'; } diff --git a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts index 6db0607a62fc..bf1d96befb4c 100644 --- a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts +++ b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts @@ -35,9 +35,16 @@ export const newPropertyTypes: PagePropertyType[] = [ PagePropertyType.Number, PagePropertyType.Checkbox, PagePropertyType.Date, + PagePropertyType.CreatedBy, + PagePropertyType.UpdatedBy, // TODO(@Peng): add more ]; +export const readonlyPropertyTypes: PagePropertyType[] = [ + PagePropertyType.CreatedBy, + PagePropertyType.UpdatedBy, +]; + export class PagePropertiesMetaManager { constructor(private readonly adapter: WorkspacePropertiesAdapter) {} @@ -95,6 +102,7 @@ export class PagePropertiesMetaManager { type, order: newOrder, icon: icon ?? getDefaultIconName(type), + readonly: readonlyPropertyTypes.includes(type) || undefined, } as const; this.customPropertiesSchema[id] = property; return property; diff --git a/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx b/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx index f8942808988e..465ec74cafc3 100644 --- a/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx @@ -1,14 +1,28 @@ -import { Checkbox, DatePicker, Menu } from '@affine/component'; +import { Avatar, Checkbox, DatePicker, Menu } from '@affine/component'; +import { CloudDocMetaService } from '@affine/core/modules/cloud/services/cloud-doc-meta'; import type { PageInfoCustomProperty, PageInfoCustomPropertyMeta, PagePropertyType, } from '@affine/core/modules/properties/services/schema'; +import { WorkspaceFlavour } from '@affine/env/workspace'; import { i18nTime, useI18n } from '@affine/i18n'; -import { DocService, useService } from '@toeverything/infra'; +import { + DocService, + useLiveData, + useService, + WorkspaceService, +} from '@toeverything/infra'; import { noop } from 'lodash-es'; import type { ChangeEventHandler } from 'react'; -import { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { managerContext } from './common'; import * as styles from './styles.css'; @@ -190,6 +204,102 @@ export const TagsValue = () => { ); }; +const CloudUserAvatar = (props: { type: 'CreatedBy' | 'UpdatedBy' }) => { + const cloudDocMetaService = useService(CloudDocMetaService); + const cloudDocMeta = useLiveData(cloudDocMetaService.cloudDocMeta.meta$); + const isRevalidating = useLiveData( + cloudDocMetaService.cloudDocMeta.isRevalidating$ + ); + const error = useLiveData(cloudDocMetaService.cloudDocMeta.error$); + + useEffect(() => { + cloudDocMetaService.cloudDocMeta.revalidate(); + }, [cloudDocMetaService]); + + const user = useMemo(() => { + if (!cloudDocMeta) return null; + if (props.type === 'CreatedBy' && cloudDocMeta.createdBy) { + return { + name: cloudDocMeta.createdBy.name, + avatarUrl: cloudDocMeta.createdBy.avatarUrl, + }; + } else if (props.type === 'UpdatedBy' && cloudDocMeta.updatedBy) { + return { + name: cloudDocMeta.updatedBy.name, + avatarUrl: cloudDocMeta.updatedBy.avatarUrl, + }; + } + return null; + }, [cloudDocMeta, props.type]); + + const t = useI18n(); + + if (!cloudDocMeta) { + if (isRevalidating) { + // TODO: loading ui + return null; + } + if (error) { + // error ui + return; + } + return null; + } + if (user) { + return ( + <> + + {user.name} + + ); + } + return ( + <> + + + {t['com.affine.page-properties.property-user-avatar-no-record']()} + + + ); +}; + +export const LocalUserValue = () => { + const t = useI18n(); + return {t['com.affine.page-properties.local-user']()}; +}; + +export const CreatedUserValue = () => { + const workspaceService = useService(WorkspaceService); + const isCloud = + workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD; + + if (!isCloud) { + return ; + } + + return ( +
+ +
+ ); +}; + +export const UpdatedUserValue = () => { + const workspaceService = useService(WorkspaceService); + const isCloud = + workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD; + + if (!isCloud) { + return ; + } + + return ( +
+ +
+ ); +}; + export const propertyValueRenderers: Record< PagePropertyType, typeof DateValue @@ -198,6 +308,8 @@ export const propertyValueRenderers: Record< checkbox: CheckboxValue, text: TextValue, number: NumberValue, + createdBy: CreatedUserValue, + updatedBy: UpdatedUserValue, // TODO(@Peng): fix following tags: TagsValue, progress: TextValue, diff --git a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts index d34aed52aa95..037956ce0e5c 100644 --- a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts +++ b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts @@ -361,6 +361,16 @@ export const propertyRowValueTextCell = style([ }, ]); +export const propertyRowValueUserCell = style([ + propertyRowValueCell, + { + border: 'none', + overflow: 'hidden', + columnGap: '0.5rem', + alignItems: 'center', + }, +]); + export const propertyRowValueTextarea = style([ propertyRowValueCell, { diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx index df0d983c347f..d92495737d12 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/experimental-features/index.tsx @@ -114,7 +114,11 @@ const ExperimentalFeaturesItem = ({ flag }: { flag: Flag }) => { }, [flag] ); - const link = flag.feedbackType ? feedbackLink[flag.feedbackType] : undefined; + const link = flag.feedbackType + ? flag.feedbackLink + ? flag.feedbackLink + : feedbackLink[flag.feedbackType] + : undefined; if (flag.configurable === false) { return null; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx index 30bc06e09430..5017d7d50aeb 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx @@ -279,29 +279,51 @@ const CustomPropertyRowsList = ({ return ; } else { - const required = properties.filter(property => property.required); - const optional = properties.filter(property => !property.required); + const partition = Object.groupBy(properties, p => + p.required ? 'required' : p.readonly ? 'readonly' : 'optional' + ); + return ( <> - {required.length > 0 ? ( + {partition.required && partition.required.length > 0 ? ( <>
{t[ 'com.affine.settings.workspace.properties.required-properties' ]()}
- + ) : null} - {optional.length > 0 ? ( + {partition.optional && partition.optional.length > 0 ? ( <>
{t[ 'com.affine.settings.workspace.properties.general-properties' ]()}
- + + + ) : null} + + {partition.readonly && partition.readonly.length > 0 ? ( + <> +
+ {t[ + 'com.affine.settings.workspace.properties.readonly-properties' + ]()} +
+ ) : null} diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts index fbe34c0c58ed..4d13c287d1c3 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/index.css.ts @@ -38,7 +38,16 @@ export const DoneIconStyle = style({ export const exportItemStyle = style({ padding: '4px', transition: 'all 0.3s', + gap: '0px', }); +globalStyle(`${exportItemStyle} > div:first-child`, { + alignItems: 'center', +}); +globalStyle(`${exportItemStyle} svg`, { + width: '16px', + height: '16px', +}); + export const copyLinkContainerStyle = style({ padding: '4px', display: 'flex', diff --git a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx index 45eee2a5120c..e2839950ec00 100644 --- a/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx +++ b/packages/frontend/core/src/components/affine/share-page-modal/share-menu/share-export.tsx @@ -20,7 +20,7 @@ export const ShareExport = () => {
{t['com.affine.share-menu.ShareViaExportDescription']()}
-
+
exportHandler('pdf')} className={className} type="pdf" - icon={} + icon={} label={t['com.affine.export.print']()} /> ); diff --git a/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts b/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts new file mode 100644 index 000000000000..94e30a0f8253 --- /dev/null +++ b/packages/frontend/core/src/modules/cloud/entities/cloud-doc-meta.ts @@ -0,0 +1,66 @@ +import type { GetWorkspacePageMetaByIdQuery } from '@affine/graphql'; +import type { DocService, GlobalCache } from '@toeverything/infra'; +import { + backoffRetry, + catchErrorInto, + effect, + Entity, + exhaustMapWithTrailing, + fromPromise, + LiveData, + onComplete, + onStart, +} from '@toeverything/infra'; +import { EMPTY, mergeMap } from 'rxjs'; + +import { isBackendError, isNetworkError } from '../error'; +import type { CloudDocMetaStore } from '../stores/cloud-doc-meta'; + +export type CloudDocMetaType = + GetWorkspacePageMetaByIdQuery['workspace']['pageMeta']; + +const CACHE_KEY_PREFIX = 'cloud-doc-meta:'; + +export class CloudDocMeta extends Entity { + constructor( + private readonly store: CloudDocMetaStore, + private readonly docService: DocService, + private readonly cache: GlobalCache + ) { + super(); + } + + readonly docId = this.docService.doc.id; + readonly workspaceId = this.docService.doc.workspace.id; + + readonly cacheKey = `${CACHE_KEY_PREFIX}${this.workspaceId}:${this.docId}`; + meta$ = LiveData.from( + this.cache.watch(this.cacheKey), + undefined + ); + isRevalidating$ = new LiveData(false); + error$ = new LiveData(null); + + revalidate = effect( + exhaustMapWithTrailing(() => { + return fromPromise( + this.store.fetchCloudDocMeta(this.workspaceId, this.docId) + ).pipe( + backoffRetry({ + when: isNetworkError, + count: Infinity, + }), + backoffRetry({ + when: isBackendError, + }), + mergeMap(meta => { + this.cache.set(this.cacheKey, meta); + return EMPTY; + }), + catchErrorInto(this.error$), + onStart(() => this.isRevalidating$.next(true)), + onComplete(() => this.isRevalidating$.next(false)) + ); + }) + ); +} diff --git a/packages/frontend/core/src/modules/cloud/index.ts b/packages/frontend/core/src/modules/cloud/index.ts index 499840d6ae8f..add141d3cc22 100644 --- a/packages/frontend/core/src/modules/cloud/index.ts +++ b/packages/frontend/core/src/modules/cloud/index.ts @@ -16,11 +16,15 @@ export { UserQuotaService } from './services/user-quota'; export { WebSocketService } from './services/websocket'; import { + DocScope, + DocService, type Framework, - GlobalCacheService, - GlobalStateService, + GlobalCache, + GlobalState, + WorkspaceScope, } from '@toeverything/infra'; +import { CloudDocMeta } from './entities/cloud-doc-meta'; import { ServerConfig } from './entities/server-config'; import { AuthSession } from './entities/session'; import { Subscription } from './entities/subscription'; @@ -29,6 +33,7 @@ import { UserCopilotQuota } from './entities/user-copilot-quota'; import { UserFeature } from './entities/user-feature'; import { UserQuota } from './entities/user-quota'; import { AuthService } from './services/auth'; +import { CloudDocMetaService } from './services/cloud-doc-meta'; import { FetchService } from './services/fetch'; import { GraphQLService } from './services/graphql'; import { ServerConfigService } from './services/server-config'; @@ -38,6 +43,7 @@ import { UserFeatureService } from './services/user-feature'; import { UserQuotaService } from './services/user-quota'; import { WebSocketService } from './services/websocket'; import { AuthStore } from './stores/auth'; +import { CloudDocMetaStore } from './stores/cloud-doc-meta'; import { ServerConfigStore } from './stores/server-config'; import { SubscriptionStore } from './stores/subscription'; import { UserCopilotQuotaStore } from './stores/user-copilot-quota'; @@ -53,10 +59,10 @@ export function configureCloudModule(framework: Framework) { .entity(ServerConfig, [ServerConfigStore]) .store(ServerConfigStore, [GraphQLService]) .service(AuthService, [FetchService, AuthStore]) - .store(AuthStore, [FetchService, GraphQLService, GlobalStateService]) + .store(AuthStore, [FetchService, GraphQLService, GlobalState]) .entity(AuthSession, [AuthStore]) .service(SubscriptionService, [SubscriptionStore]) - .store(SubscriptionStore, [GraphQLService, GlobalCacheService]) + .store(SubscriptionStore, [GraphQLService, GlobalCache]) .entity(Subscription, [AuthService, ServerConfigService, SubscriptionStore]) .entity(SubscriptionPrices, [ServerConfigService, SubscriptionStore]) .service(UserQuotaService) @@ -71,5 +77,10 @@ export function configureCloudModule(framework: Framework) { ]) .service(UserFeatureService) .entity(UserFeature, [AuthService, UserFeatureStore]) - .store(UserFeatureStore, [GraphQLService]); + .store(UserFeatureStore, [GraphQLService]) + .scope(WorkspaceScope) + .scope(DocScope) + .service(CloudDocMetaService) + .entity(CloudDocMeta, [CloudDocMetaStore, DocService, GlobalCache]) + .store(CloudDocMetaStore, [GraphQLService]); } diff --git a/packages/frontend/core/src/modules/cloud/services/cloud-doc-meta.ts b/packages/frontend/core/src/modules/cloud/services/cloud-doc-meta.ts new file mode 100644 index 000000000000..e1ced639bb5a --- /dev/null +++ b/packages/frontend/core/src/modules/cloud/services/cloud-doc-meta.ts @@ -0,0 +1,7 @@ +import { Service } from '@toeverything/infra'; + +import { CloudDocMeta } from '../entities/cloud-doc-meta'; + +export class CloudDocMetaService extends Service { + cloudDocMeta = this.framework.createEntity(CloudDocMeta); +} diff --git a/packages/frontend/core/src/modules/cloud/stores/auth.ts b/packages/frontend/core/src/modules/cloud/stores/auth.ts index c85ce2fcb992..535fb177815f 100644 --- a/packages/frontend/core/src/modules/cloud/stores/auth.ts +++ b/packages/frontend/core/src/modules/cloud/stores/auth.ts @@ -4,7 +4,7 @@ import { updateUserProfileMutation, uploadAvatarMutation, } from '@affine/graphql'; -import type { GlobalStateService } from '@toeverything/infra'; +import type { GlobalState } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; import type { AuthSessionInfo } from '../entities/session'; @@ -24,19 +24,17 @@ export class AuthStore extends Store { constructor( private readonly fetchService: FetchService, private readonly gqlService: GraphQLService, - private readonly globalStateService: GlobalStateService + private readonly globalState: GlobalState ) { super(); } watchCachedAuthSession() { - return this.globalStateService.globalState.watch( - 'affine-cloud-auth' - ); + return this.globalState.watch('affine-cloud-auth'); } setCachedAuthSession(session: AuthSessionInfo | null) { - this.globalStateService.globalState.set('affine-cloud-auth', session); + this.globalState.set('affine-cloud-auth', session); } async fetchSession() { diff --git a/packages/frontend/core/src/modules/cloud/stores/cloud-doc-meta.ts b/packages/frontend/core/src/modules/cloud/stores/cloud-doc-meta.ts new file mode 100644 index 000000000000..3804babf4b3c --- /dev/null +++ b/packages/frontend/core/src/modules/cloud/stores/cloud-doc-meta.ts @@ -0,0 +1,26 @@ +import { getWorkspacePageMetaByIdQuery } from '@affine/graphql'; +import { Store } from '@toeverything/infra'; + +import { type CloudDocMetaType } from '../entities/cloud-doc-meta'; +import type { GraphQLService } from '../services/graphql'; + +export class CloudDocMetaStore extends Store { + constructor(private readonly gqlService: GraphQLService) { + super(); + } + + async fetchCloudDocMeta( + workspaceId: string, + docId: string, + abortSignal?: AbortSignal + ): Promise { + const serverConfigData = await this.gqlService.gql({ + query: getWorkspacePageMetaByIdQuery, + variables: { id: workspaceId, pageId: docId }, + context: { + signal: abortSignal, + }, + }); + return serverConfigData.workspace.pageMeta; + } +} diff --git a/packages/frontend/core/src/modules/cloud/stores/subscription.ts b/packages/frontend/core/src/modules/cloud/stores/subscription.ts index d8b04ca2343d..2002dd2b4f95 100644 --- a/packages/frontend/core/src/modules/cloud/stores/subscription.ts +++ b/packages/frontend/core/src/modules/cloud/stores/subscription.ts @@ -12,7 +12,7 @@ import { subscriptionQuery, updateSubscriptionMutation, } from '@affine/graphql'; -import type { GlobalCacheService } from '@toeverything/infra'; +import type { GlobalCache } from '@toeverything/infra'; import { Store } from '@toeverything/infra'; import type { SubscriptionType } from '../entities/subscription'; @@ -37,7 +37,7 @@ const getDefaultSubscriptionSuccessCallbackLink = ( export class SubscriptionStore extends Store { constructor( private readonly gqlService: GraphQLService, - private readonly globalCacheService: GlobalCacheService + private readonly globalCache: GlobalCache ) { super(); } @@ -97,16 +97,13 @@ export class SubscriptionStore extends Store { } getCachedSubscriptions(userId: string) { - return this.globalCacheService.globalCache.get( + return this.globalCache.get( SUBSCRIPTION_CACHE_KEY + userId ); } setCachedSubscriptions(userId: string, subscriptions: SubscriptionType[]) { - return this.globalCacheService.globalCache.set( - SUBSCRIPTION_CACHE_KEY + userId, - subscriptions - ); + return this.globalCache.set(SUBSCRIPTION_CACHE_KEY + userId, subscriptions); } setSubscriptionRecurring( diff --git a/packages/frontend/core/src/modules/properties/services/schema.ts b/packages/frontend/core/src/modules/properties/services/schema.ts index b93a607b3e72..dd7c670882fc 100644 --- a/packages/frontend/core/src/modules/properties/services/schema.ts +++ b/packages/frontend/core/src/modules/properties/services/schema.ts @@ -21,6 +21,8 @@ export enum PagePropertyType { Progress = 'progress', Checkbox = 'checkbox', Tags = 'tags', + CreatedBy = 'createdBy', + UpdatedBy = 'updatedBy', } export const PagePropertyMetaBaseSchema = z.object({ @@ -30,6 +32,7 @@ export const PagePropertyMetaBaseSchema = z.object({ type: z.nativeEnum(PagePropertyType), icon: z.string(), required: z.boolean().optional(), + readonly: z.boolean().optional(), }); export const PageSystemPropertyMetaBaseSchema = diff --git a/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts b/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts index f3d83da55442..d0bb50532b34 100644 --- a/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts +++ b/packages/frontend/core/src/modules/share-doc/entities/share-docs-list.ts @@ -6,12 +6,13 @@ import { catchErrorInto, effect, Entity, + exhaustMapWithTrailing, fromPromise, LiveData, onComplete, onStart, } from '@toeverything/infra'; -import { EMPTY, mergeMap, switchMap } from 'rxjs'; +import { EMPTY, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../../cloud'; import type { ShareDocsStore } from '../stores/share-docs'; @@ -35,7 +36,7 @@ export class ShareDocsList extends Entity { } revalidate = effect( - switchMap(() => + exhaustMapWithTrailing(() => fromPromise(signal => { return this.store.getWorkspacesShareDocs( this.workspaceService.workspace.id, diff --git a/packages/frontend/electron/src/main/updater/affine-update-provider.ts b/packages/frontend/electron/src/main/updater/affine-update-provider.ts new file mode 100644 index 000000000000..e4c26780442b --- /dev/null +++ b/packages/frontend/electron/src/main/updater/affine-update-provider.ts @@ -0,0 +1,162 @@ +// credits: migrated from https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/providers/GitHubProvider.ts + +import type { CustomPublishOptions } from 'builder-util-runtime'; +import { newError } from 'builder-util-runtime'; +import type { + AppUpdater, + ResolvedUpdateFileInfo, + UpdateFileInfo, + UpdateInfo, +} from 'electron-updater'; +import { CancellationToken, Provider } from 'electron-updater'; +import type { ProviderRuntimeOptions } from 'electron-updater/out/providers/Provider'; +import { + getFileList, + parseUpdateInfo, +} from 'electron-updater/out/providers/Provider'; + +import type { buildType } from '../config'; +import { isSquirrelBuild } from './utils'; + +interface GithubUpdateInfo extends UpdateInfo { + tag: string; +} + +interface GithubRelease { + name: string; + tag_name: string; + published_at: string; + assets: Array<{ + name: string; + url: string; + }>; +} + +interface UpdateProviderOptions { + feedUrl?: string; + channel: typeof buildType; +} + +export class AFFiNEUpdateProvider extends Provider { + static configFeed(options: UpdateProviderOptions): CustomPublishOptions { + return { + provider: 'custom', + feedUrl: 'https://affine.pro/api/worker/releases', + updateProvider: AFFiNEUpdateProvider, + ...options, + }; + } + + constructor( + private readonly options: CustomPublishOptions, + _updater: AppUpdater, + runtimeOptions: ProviderRuntimeOptions + ) { + super(runtimeOptions); + } + + get feedUrl(): URL { + const url = new URL(this.options.feedUrl); + url.searchParams.set('channel', this.options.channel); + url.searchParams.set('minimal', 'true'); + + return url; + } + + async getLatestVersion(): Promise { + const cancellationToken = new CancellationToken(); + + const releasesJsonStr = await this.httpRequest( + this.feedUrl, + { + accept: 'application/json', + 'cache-control': 'no-cache', + }, + cancellationToken + ); + + if (!releasesJsonStr) { + throw new Error( + `Failed to get releases from ${this.feedUrl.toString()}, response is empty` + ); + } + + const releases = JSON.parse(releasesJsonStr); + + if (releases.length === 0) { + throw new Error( + `No published versions in channel ${this.options.channel}` + ); + } + + const latestRelease = releases[0] as GithubRelease; + const tag = latestRelease.tag_name; + + const channelFileName = getChannelFilename(this.getDefaultChannelName()); + const channelFileAsset = latestRelease.assets.find(({ url }) => + url.endsWith(channelFileName) + ); + + if (!channelFileAsset) { + throw newError( + `Cannot find ${channelFileName} in the latest release artifacts.`, + 'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND' + ); + } + + const channelFileUrl = new URL(channelFileAsset.url); + const channelFileContent = await this.httpRequest(channelFileUrl); + + const result = parseUpdateInfo( + channelFileContent, + channelFileName, + channelFileUrl + ); + + const files: UpdateFileInfo[] = []; + + result.files.forEach(file => { + const asset = latestRelease.assets.find(({ name }) => name === file.url); + if (asset) { + file.url = asset.url; + } + + // for windows, we need to determine its installer type (nsis or squirrel) + if (process.platform === 'win32') { + const isSquirrel = isSquirrelBuild(); + if (isSquirrel && file.url.endsWith('.nsis.exe')) { + return; + } + } + + files.push(file); + }); + + if (result.releaseName == null) { + result.releaseName = latestRelease.name; + } + + if (result.releaseNotes == null) { + // TODO(@forehalo): add release notes + result.releaseNotes = ''; + } + + return { + tag: tag, + ...result, + }; + } + + resolveFiles(updateInfo: GithubUpdateInfo): Array { + const files = getFileList(updateInfo); + + return files.map(file => ({ + url: new URL(file.url), + info: file, + })); + } +} + +function getChannelFilename(channel: string): string { + return `${channel}.yml`; +} diff --git a/packages/frontend/electron/src/main/updater/custom-github-provider.ts b/packages/frontend/electron/src/main/updater/custom-github-provider.ts deleted file mode 100644 index 605f308f9cdc..000000000000 --- a/packages/frontend/electron/src/main/updater/custom-github-provider.ts +++ /dev/null @@ -1,332 +0,0 @@ -// credits: migrated from https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/providers/GitHubProvider.ts - -import type { - CustomPublishOptions, - GithubOptions, - ReleaseNoteInfo, - XElement, -} from 'builder-util-runtime'; -import { HttpError, newError, parseXml } from 'builder-util-runtime'; -import type { - AppUpdater, - ResolvedUpdateFileInfo, - UpdateInfo, -} from 'electron-updater'; -import { CancellationToken } from 'electron-updater'; -import { BaseGitHubProvider } from 'electron-updater/out/providers/GitHubProvider'; -import type { ProviderRuntimeOptions } from 'electron-updater/out/providers/Provider'; -import { - parseUpdateInfo, - resolveFiles, -} from 'electron-updater/out/providers/Provider'; -import * as semver from 'semver'; - -import { isSquirrelBuild } from './utils'; - -interface GithubUpdateInfo extends UpdateInfo { - tag: string; -} - -interface GithubRelease { - id: number; - tag_name: string; - target_commitish: string; - name: string; - draft: boolean; - prerelease: boolean; - created_at: string; - published_at: string; -} - -const hrefRegExp = /\/tag\/([^/]+)$/; - -export class CustomGitHubProvider extends BaseGitHubProvider { - constructor( - options: CustomPublishOptions, - private readonly updater: AppUpdater, - runtimeOptions: ProviderRuntimeOptions - ) { - super(options as unknown as GithubOptions, 'github.com', runtimeOptions); - } - - async getLatestVersion(): Promise { - const cancellationToken = new CancellationToken(); - - const feedXml = await this.httpRequest( - newUrlFromBase(`${this.basePath}.atom`, this.baseUrl), - { - accept: 'application/xml, application/atom+xml, text/xml, */*', - }, - cancellationToken - ); - - if (!feedXml) { - throw new Error( - `Cannot find feed in the remote server (${this.baseUrl.href})` - ); - } - - const feed = parseXml(feedXml); - // noinspection TypeScriptValidateJSTypes - let latestRelease = feed.element( - 'entry', - false, - `No published versions on GitHub` - ); - let tag: string | null = null; - try { - const currentChannel = - this.options.channel || - this.updater?.channel || - (semver.prerelease(this.updater.currentVersion)?.[0] as string) || - null; - - if (currentChannel === null) { - throw newError( - `Cannot parse channel from version: ${this.updater.currentVersion}`, - 'ERR_UPDATER_INVALID_VERSION' - ); - } - - const releaseTag = await this.getLatestTagByRelease( - currentChannel, - cancellationToken - ); - for (const element of feed.getElements('entry')) { - // noinspection TypeScriptValidateJSTypes - const hrefElement = hrefRegExp.exec( - element.element('link').attribute('href') - ); - - // If this is null then something is wrong and skip this release - if (hrefElement === null) continue; - - // This Release's Tag - const hrefTag = hrefElement[1]; - // Get Channel from this release's tag - // If it is null, we believe it is stable version - const hrefChannel = - (semver.prerelease(hrefTag)?.[0] as string) || 'stable'; - - let isNextPreRelease = false; - if (releaseTag) { - isNextPreRelease = releaseTag === hrefTag; - } else { - isNextPreRelease = hrefChannel === currentChannel; - } - - if (isNextPreRelease) { - tag = hrefTag; - latestRelease = element; - break; - } - } - } catch (e: any) { - throw newError( - `Cannot parse releases feed: ${ - e.stack || e.message - },\nXML:\n${feedXml}`, - 'ERR_UPDATER_INVALID_RELEASE_FEED' - ); - } - - if (tag === null || tag === undefined) { - throw newError( - `No published versions on GitHub`, - 'ERR_UPDATER_NO_PUBLISHED_VERSIONS' - ); - } - - let rawData: string | null = null; - let channelFile = ''; - let channelFileUrl: any = ''; - const fetchData = async (channelName: string) => { - channelFile = getChannelFilename(channelName); - channelFileUrl = newUrlFromBase( - this.getBaseDownloadPath(String(tag), channelFile), - this.baseUrl - ); - const requestOptions = this.createRequestOptions(channelFileUrl); - try { - return await this.executor.request(requestOptions, cancellationToken); - } catch (e: any) { - if (e instanceof HttpError && e.statusCode === 404) { - throw newError( - `Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${ - e.stack || e.message - }`, - 'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND' - ); - } - throw e; - } - }; - - try { - const channel = this.updater.allowPrerelease - ? this.getCustomChannelName( - String(semver.prerelease(tag)?.[0] || 'latest') - ) - : this.getDefaultChannelName(); - rawData = await fetchData(channel); - } catch (e: any) { - if (this.updater.allowPrerelease) { - // Allow fallback to `latest.yml` - rawData = await fetchData(this.getDefaultChannelName()); - } else { - throw e; - } - } - - const result = parseUpdateInfo(rawData, channelFile, channelFileUrl); - if (result.releaseName == null) { - result.releaseName = latestRelease.elementValueOrEmpty('title'); - } - - if (result.releaseNotes == null) { - result.releaseNotes = computeReleaseNotes( - this.updater.currentVersion, - this.updater.fullChangelog, - feed, - latestRelease - ); - } - return { - tag: tag, - ...result, - }; - } - - private get basePath(): string { - return `/${this.options.owner}/${this.options.repo}/releases`; - } - - /** - * Use release api to get latest version to filter draft version. - * But this api have low request limit 60-times/1-hour, use this to help, not depend on it - * https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28 - * https://api.github.com/repos/toeverything/affine/releases - * https://docs.github.com/en/rest/rate-limit/rate-limit?apiVersion=2022-11-28#about-rate-limits - */ - private async getLatestTagByRelease( - currentChannel: string, - cancellationToken: CancellationToken - ) { - try { - const releasesStr = await this.httpRequest( - newUrlFromBase(`/repos${this.basePath}`, this.baseApiUrl), - { - accept: 'Accept: application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - cancellationToken - ); - - if (!releasesStr) { - return null; - } - - const releases: GithubRelease[] = JSON.parse(releasesStr); - for (const release of releases) { - if (release.draft) { - continue; - } - - const releaseTag = release.tag_name; - const releaseChannel = - (semver.prerelease(releaseTag)?.[0] as string) || 'stable'; - if (releaseChannel === currentChannel) { - return release.tag_name; - } - } - } catch (e: any) { - console.info(`Cannot parse release: ${e.stack || e.message}`); - } - - return null; - } - - resolveFiles(updateInfo: GithubUpdateInfo): Array { - const filteredUpdateInfo = structuredClone(updateInfo); - // for windows, we need to determine its installer type (nsis or squirrel) - if (process.platform === 'win32' && updateInfo.files.length > 1) { - const isSquirrel = isSquirrelBuild(); - // @ts-expect-error we should be able to modify the object - filteredUpdateInfo.files = updateInfo.files.filter(file => { - return isSquirrel - ? !file.url.includes('nsis.exe') - : file.url.includes('nsis.exe'); - }); - } - - // still replace space to - due to backward compatibility - return resolveFiles(filteredUpdateInfo, this.baseUrl, p => - this.getBaseDownloadPath(filteredUpdateInfo.tag, p.replace(/ /g, '-')) - ); - } - - private getBaseDownloadPath(tag: string, fileName: string): string { - return `${this.basePath}/download/${tag}/${fileName}`; - } -} - -export interface CustomGitHubOptions { - channel: string; - repo: string; - owner: string; - releaseType: 'release' | 'prerelease'; -} - -function getNoteValue(parent: XElement): string { - const result = parent.elementValueOrEmpty('content'); - // GitHub reports empty notes as No content. - return result === 'No content.' ? '' : result; -} - -export function computeReleaseNotes( - currentVersion: semver.SemVer, - isFullChangelog: boolean, - feed: XElement, - latestRelease: any -): string | Array | null { - if (!isFullChangelog) { - return getNoteValue(latestRelease); - } - - const releaseNotes: Array = []; - for (const release of feed.getElements('entry')) { - // noinspection TypeScriptValidateJSTypes - const versionRelease = /\/tag\/v?([^/]+)$/.exec( - release.element('link').attribute('href') - )?.[1]; - if (versionRelease && semver.lt(currentVersion, versionRelease)) { - releaseNotes.push({ - version: versionRelease, - note: getNoteValue(release), - }); - } - } - return releaseNotes.sort((a, b) => semver.rcompare(a.version, b.version)); -} - -// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number, -// so, it makes sense only for Generic Provider for channel files -function newUrlFromBase( - pathname: string, - baseUrl: URL, - addRandomQueryToAvoidCaching = false -): URL { - const result = new URL(pathname, baseUrl); - // search is not propagated (search is an empty string if not specified) - const search = baseUrl.search; - if (search != null && search.length !== 0) { - result.search = search; - } else if (addRandomQueryToAvoidCaching) { - result.search = `noCache=${Date.now().toString(32)}`; - } - return result; -} - -function getChannelFilename(channel: string): string { - return `${channel}.yml`; -} diff --git a/packages/frontend/electron/src/main/updater/electron-updater.ts b/packages/frontend/electron/src/main/updater/electron-updater.ts index 401132019e5f..a32d75fbedcc 100644 --- a/packages/frontend/electron/src/main/updater/electron-updater.ts +++ b/packages/frontend/electron/src/main/updater/electron-updater.ts @@ -3,7 +3,7 @@ import { autoUpdater as defaultAutoUpdater } from 'electron-updater'; import { buildType } from '../config'; import { logger } from '../logger'; -import { CustomGitHubProvider } from './custom-github-provider'; +import { AFFiNEUpdateProvider } from './affine-update-provider'; import { updaterSubjects } from './event'; import { WindowsUpdater } from './windows-updater'; @@ -93,16 +93,9 @@ export const registerUpdater = async () => { autoUpdater.autoInstallOnAppQuit = false; autoUpdater.autoRunAppAfterInstall = true; - const feedUrl: Parameters[0] = { + const feedUrl = AFFiNEUpdateProvider.configFeed({ channel: buildType, - // hack for custom provider - provider: 'custom' as 'github', - repo: buildType !== 'internal' ? 'AFFiNE' : 'AFFiNE-Releases', - owner: 'toeverything', - releaseType: buildType === 'stable' ? 'release' : 'prerelease', - // @ts-expect-error hack for custom provider - updateProvider: CustomGitHubProvider, - }; + }); logger.debug('auto-updater feed config', feedUrl); diff --git a/packages/frontend/electron/test/main/fixtures/candidates/beta.json b/packages/frontend/electron/test/main/fixtures/candidates/beta.json new file mode 100644 index 000000000000..9933b98e697f --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/candidates/beta.json @@ -0,0 +1,75 @@ +[ + { + "url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.16.3-beta.2", + "name": "0.16.3-beta.2", + "tag_name": "v0.16.3-beta.2", + "published_at": "2024-08-14T06:56:25Z", + "assets": [ + { + "name": "affine-0.16.3-beta.2-beta-linux-x64.appimage", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-linux-x64.appimage", + "size": 178308288 + }, + { + "name": "affine-0.16.3-beta.2-beta-linux-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-linux-x64.zip", + "size": 176402697 + }, + { + "name": "affine-0.16.3-beta.2-beta-macos-arm64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-macos-arm64.dmg", + "size": 168063426 + }, + { + "name": "affine-0.16.3-beta.2-beta-macos-arm64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-macos-arm64.zip", + "size": 167528680 + }, + { + "name": "affine-0.16.3-beta.2-beta-macos-x64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-macos-x64.dmg", + "size": 175049454 + }, + { + "name": "affine-0.16.3-beta.2-beta-macos-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-macos-x64.zip", + "size": 174740492 + }, + { + "name": "affine-0.16.3-beta.2-beta-windows-x64.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-windows-x64.exe", + "size": 177771240 + }, + { + "name": "affine-0.16.3-beta.2-beta-windows-x64.nsis.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/affine-0.16.3-beta.2-beta-windows-x64.nsis.exe", + "size": 130309240 + }, + { + "name": "codecov.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/codecov.yml", + "size": 91 + }, + { + "name": "latest-linux.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/latest-linux.yml", + "size": 561 + }, + { + "name": "latest-mac.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/latest-mac.yml", + "size": 897 + }, + { + "name": "latest.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/latest.yml", + "size": 562 + }, + { + "name": "web-static.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3-beta.2/web-static.zip", + "size": 61990155 + } + ] + } +] diff --git a/packages/frontend/electron/test/main/fixtures/candidates/canary.json b/packages/frontend/electron/test/main/fixtures/candidates/canary.json new file mode 100644 index 000000000000..8f68b59b314d --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/candidates/canary.json @@ -0,0 +1,75 @@ +[ + { + "url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.17.0-canary.7", + "name": "0.17.0-canary.7", + "tag_name": "v0.17.0-canary.7", + "published_at": "2024-08-29T08:20:54Z", + "assets": [ + { + "name": "affine-0.17.0-canary.7-canary-linux-x64.appimage", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-linux-x64.appimage", + "size": 181990592 + }, + { + "name": "affine-0.17.0-canary.7-canary-linux-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-linux-x64.zip", + "size": 180105256 + }, + { + "name": "affine-0.17.0-canary.7-canary-macos-arm64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-macos-arm64.dmg", + "size": 170556866 + }, + { + "name": "affine-0.17.0-canary.7-canary-macos-arm64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-macos-arm64.zip", + "size": 170382513 + }, + { + "name": "affine-0.17.0-canary.7-canary-macos-x64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-macos-x64.dmg", + "size": 176815834 + }, + { + "name": "affine-0.17.0-canary.7-canary-macos-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-macos-x64.zip", + "size": 176948223 + }, + { + "name": "affine-0.17.0-canary.7-canary-windows-x64.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-windows-x64.exe", + "size": 182557416 + }, + { + "name": "affine-0.17.0-canary.7-canary-windows-x64.nsis.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/affine-0.17.0-canary.7-canary-windows-x64.nsis.exe", + "size": 133493672 + }, + { + "name": "codecov.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/codecov.yml", + "size": 91 + }, + { + "name": "latest-linux.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/latest-linux.yml", + "size": 575 + }, + { + "name": "latest-mac.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/latest-mac.yml", + "size": 919 + }, + { + "name": "latest.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/latest.yml", + "size": 576 + }, + { + "name": "web-static.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.17.0-canary.7/web-static.zip", + "size": 61555023 + } + ] + } +] diff --git a/packages/frontend/electron/test/main/fixtures/candidates/stable.json b/packages/frontend/electron/test/main/fixtures/candidates/stable.json new file mode 100644 index 000000000000..8b5df32feb6a --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/candidates/stable.json @@ -0,0 +1,75 @@ +[ + { + "url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.16.3", + "name": "0.16.3", + "tag_name": "v0.16.3", + "published_at": "2024-08-14T07:43:22Z", + "assets": [ + { + "name": "affine-0.16.3-stable-linux-x64.appimage", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-linux-x64.appimage", + "size": 178308288 + }, + { + "name": "affine-0.16.3-stable-linux-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-linux-x64.zip", + "size": 176405078 + }, + { + "name": "affine-0.16.3-stable-macos-arm64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-macos-arm64.dmg", + "size": 168093091 + }, + { + "name": "affine-0.16.3-stable-macos-arm64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-macos-arm64.zip", + "size": 167540517 + }, + { + "name": "affine-0.16.3-stable-macos-x64.dmg", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-macos-x64.dmg", + "size": 175029125 + }, + { + "name": "affine-0.16.3-stable-macos-x64.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-macos-x64.zip", + "size": 174752343 + }, + { + "name": "affine-0.16.3-stable-windows-x64.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-windows-x64.exe", + "size": 177757416 + }, + { + "name": "affine-0.16.3-stable-windows-x64.nsis.exe", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/affine-0.16.3-stable-windows-x64.nsis.exe", + "size": 130302976 + }, + { + "name": "codecov.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/codecov.yml", + "size": 91 + }, + { + "name": "latest-linux.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/latest-linux.yml", + "size": 539 + }, + { + "name": "latest-mac.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/latest-mac.yml", + "size": 865 + }, + { + "name": "latest.yml", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/latest.yml", + "size": 540 + }, + { + "name": "web-static.zip", + "url": "https://github.com/toeverything/AFFiNE/releases/download/v0.16.3/web-static.zip", + "size": 61989498 + } + ] + } +] diff --git a/packages/frontend/electron/test/main/fixtures/feeds.txt b/packages/frontend/electron/test/main/fixtures/feeds.txt deleted file mode 100644 index 479db4e7f473..000000000000 --- a/packages/frontend/electron/test/main/fixtures/feeds.txt +++ /dev/null @@ -1,103 +0,0 @@ - - - tag:github.com,2008:https://github.com/toeverything/AFFiNE/releases - - - Release notes from AFFiNE - 2023-12-28T16:36:36+08:00 - - tag:github.com,2008:Repository/519859998/0.11.0-nightly-202312280901-e11e827 - 2023-12-28T17:23:15+08:00 - - 0.11.0-nightly-202312280901-e11e827 - No content. - - github-actions[bot] - - - - - tag:github.com,2008:Repository/519859998/v0.11.1 - 2023-12-27T19:40:15+08:00 - - 0.11.1 - <p>fix(core): enable page history for beta/stable (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056965718" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5415" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5415/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5415">#5415</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(component): fix font display on safari (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055383919" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5393" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5393/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5393">#5393</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -fix(core): avatars are not aligned (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056136302" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5404" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5404/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5404">#5404</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a><br> -fix(core): trash page footer display issue (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056129348" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5402" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5402/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5402">#5402</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(electron): set stable base url to app.affine.pro (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056113464" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5401" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5401/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5401">#5401</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/joooye34/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/joooye34">@joooye34</a><br> -fix(core): about setting blink issue (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056090521" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5399" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5399/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5399">#5399</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(core): workpace list blink issue on open (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056096694" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5400" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5400/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5400">#5400</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -chore(core): add background color to questionnaire (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055986563" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5396" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5396/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5396">#5396</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a><br> -fix(core): correct title of onboarding article-2 (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053650286" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5387" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5387/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5387">#5387</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/CatsJuice/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/CatsJuice">@CatsJuice</a><br> -fix: use prefix in electron to prevent formdata bug (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055616549" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5395" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5395/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5395">#5395</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/darkskygit/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/darkskygit">@darkskygit</a><br> -fix(core): fix flickering workspace list (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055363698" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5391" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5391/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5391">#5391</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -fix(workspace): fix svg file with xml header (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053693777" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5388" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5388/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5388">#5388</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -feat: bump blocksuite (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053645163" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5386" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5386/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5386">#5386</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/regischen/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/regischen">@regischen</a></p> - - github-actions[bot] - - - - - tag:github.com,2008:Repository/519859998/v0.11.1-beta.1 - 2023-12-27T18:30:52+08:00 - - 0.11.1-beta.1 - <p>fix(core): enable page history for beta/stable (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056965718" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5415" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5415/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5415">#5415</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(component): fix font display on safari (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055383919" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5393" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5393/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5393">#5393</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -fix(core): avatars are not aligned (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056136302" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5404" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5404/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5404">#5404</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a><br> -fix(core): trash page footer display issue (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056129348" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5402" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5402/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5402">#5402</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(electron): set stable base url to app.affine.pro (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056113464" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5401" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5401/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5401">#5401</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/joooye34/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/joooye34">@joooye34</a><br> -fix(core): about setting blink issue (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056090521" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5399" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5399/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5399">#5399</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -fix(core): workpace list blink issue on open (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056096694" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5400" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5400/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5400">#5400</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a><br> -chore(core): add background color to questionnaire (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055986563" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5396" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5396/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5396">#5396</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a><br> -fix(core): correct title of onboarding article-2 (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053650286" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5387" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5387/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5387">#5387</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/CatsJuice/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/CatsJuice">@CatsJuice</a><br> -fix: use prefix in electron to prevent formdata bug (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055616549" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5395" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5395/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5395">#5395</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/darkskygit/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/darkskygit">@darkskygit</a><br> -fix(core): fix flickering workspace list (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055363698" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5391" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5391/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5391">#5391</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -fix(workspace): fix svg file with xml header (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053693777" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5388" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5388/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5388">#5388</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/EYHN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/EYHN">@EYHN</a><br> -feat: bump blocksuite (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053645163" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5386" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5386/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5386">#5386</a>) <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/regischen/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/regischen">@regischen</a></p> - - github-actions[bot] - - - - - tag:github.com,2008:Repository/519859998/v0.11.1-canary.2 - 2023-12-28T10:47:52+08:00 - - 0.11.1-canary.2 - No content. - - github-actions[bot] - - - - - tag:github.com,2008:Repository/519859998/v0.11.1-canary.1 - 2023-12-27T10:47:52+08:00 - - 0.11.1-canary.1 - <h2>What's Changed</h2> -<ul> -<li>fix(core): remove plugins settings by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2048206391" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5337" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5337/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5337">#5337</a></li> -<li>chore: bump up @vitejs/plugin-vue version to v5 by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/renovate/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/renovate">@renovate</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055571407" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5394" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5394/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5394">#5394</a></li> -<li>fix(core): correct title of onboarding article-2 by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/CatsJuice/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/CatsJuice">@CatsJuice</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053650286" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5387" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5387/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5387">#5387</a></li> -<li>chore(core): add background color to questionnaire by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2055986563" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5396" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5396/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5396">#5396</a></li> -<li>ci: define tag name to fix nightly release failing by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/joooye34/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/joooye34">@joooye34</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056068417" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5397" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5397/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5397">#5397</a></li> -<li>fix(core): workpace list blink issue on open by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056096694" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5400" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5400/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5400">#5400</a></li> -<li>fix(core): about setting blink issue by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056090521" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5399" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5399/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5399">#5399</a></li> -<li>fix(electron): set stable base url to app.affine.pro by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/joooye34/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/joooye34">@joooye34</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056113464" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5401" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5401/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5401">#5401</a></li> -<li>fix(core): trash page footer display issue by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056129348" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5402" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5402/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5402">#5402</a></li> -<li>chore: bump up @types/supertest version to v6 by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/renovate/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/renovate">@renovate</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2053226342" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5376" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5376/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5376">#5376</a></li> -<li>chore: bump up react-i18next version to v14 by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/renovate/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/renovate">@renovate</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2052675317" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5375" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5375/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5375">#5375</a></li> -<li>fix(core): avatars are not aligned by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/JimmFly/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/JimmFly">@JimmFly</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056136302" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5404" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5404/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5404">#5404</a></li> -<li>fix(infra): workaround for self-referencing in storybook by <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pengx17/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pengx17">@pengx17</a> in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2056165203" data-permission-text="Title is private" data-url="https://github.com/toeverything/AFFiNE/issues/5406" data-hovercard-type="pull_request" data-hovercard-url="/toeverything/AFFiNE/pull/5406/hovercard" href="https://github.com/toeverything/AFFiNE/pull/5406">#5406</a></li> -</ul> -<p><strong>Full Changelog</strong>: <a class="commit-link" href="https://github.com/toeverything/AFFiNE/compare/v0.11.1-canary.0...v0.11.1-canary.1"><tt>v0.11.1-canary.0...v0.11.1-canary.1</tt></a></p> - - github-actions[bot] - - - - diff --git a/packages/frontend/electron/test/main/fixtures/release-list.txt b/packages/frontend/electron/test/main/fixtures/release-list.txt deleted file mode 100644 index 172fd73260ad..000000000000 --- a/packages/frontend/electron/test/main/fixtures/release-list.txt +++ /dev/null @@ -1,94 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135252810", - "assets_url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135252810/assets", - "upload_url": "https://uploads.github.com/repos/toeverything/AFFiNE/releases/135252810/assets{?name,label}", - "html_url": "https://github.com/toeverything/AFFiNE/releases/tag/0.11.0-nightly-202312280901-e11e827", - "id": 135252810, - "node_id": "RE_kwDOHvxvHs4ID8tK", - "tag_name": "0.11.0-nightly-202312280901-e11e827", - "target_commitish": "canary", - "name": "0.11.0-nightly-202312280901-e11e827", - "draft": false, - "prerelease": true, - "created_at": "2023-12-28T08:36:36Z", - "published_at": "2023-12-28T09:23:07Z", - "tarball_url": "https://api.github.com/repos/toeverything/AFFiNE/tarball/0.11.0-nightly-202312280901-e11e827", - "zipball_url": "https://api.github.com/repos/toeverything/AFFiNE/zipball/0.11.0-nightly-202312280901-e11e827", - "body": "" - }, - { - "url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135173430", - "assets_url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135173430/assets", - "upload_url": "https://uploads.github.com/repos/toeverything/AFFiNE/releases/135173430/assets{?name,label}", - "html_url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.11.1", - "id": 135173430, - "node_id": "RE_kwDOHvxvHs4IDpU2", - "tag_name": "v0.11.1", - "target_commitish": "canary", - "name": "0.11.1", - "draft": false, - "prerelease": false, - "created_at": "2023-12-27T06:39:59Z", - "published_at": "2023-12-27T11:40:15Z", - "tarball_url": "https://api.github.com/repos/toeverything/AFFiNE/tarball/v0.11.1", - "zipball_url": "https://api.github.com/repos/toeverything/AFFiNE/zipball/v0.11.1", - "mentions_count": 7 - }, - { - "url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135163918", - "assets_url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135163918/assets", - "upload_url": "https://uploads.github.com/repos/toeverything/AFFiNE/releases/135163918/assets{?name,label}", - "html_url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.11.1-beta.1", - "id": 135163918, - "node_id": "RE_kwDOHvxvHs4IDnAO", - "tag_name": "v0.11.1-beta.1", - "target_commitish": "canary", - "name": "0.11.1-beta.1", - "draft": false, - "prerelease": true, - "created_at": "2023-12-27T06:39:59Z", - "published_at": "2023-12-27T10:30:52Z", - "tarball_url": "https://api.github.com/repos/toeverything/AFFiNE/tarball/v0.11.1-beta.1", - "zipball_url": "https://api.github.com/repos/toeverything/AFFiNE/zipball/v0.11.1-beta.1", - "mentions_count": 7 - }, - { - "url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135103520", - "assets_url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135103520/assets", - "upload_url": "https://uploads.github.com/repos/toeverything/AFFiNE/releases/135103520/assets{?name,label}", - "html_url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.11.1-canary.1", - "id": 135103520, - "node_id": "RE_kwDOHvxvHs4IDYQg", - "tag_name": "v0.11.1-canary.2", - "target_commitish": "canary", - "name": "0.11.1-canary.2", - "draft": true, - "prerelease": true, - "created_at": "2023-12-26T12:27:20Z", - "published_at": "2023-12-26T13:24:28Z", - "tarball_url": "https://api.github.com/repos/toeverything/AFFiNE/tarball/v0.11.1-canary.1", - "zipball_url": "https://api.github.com/repos/toeverything/AFFiNE/zipball/v0.11.1-canary.1", - "body": "## What's Changed\r\n* fix(core): remove plugins settings by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5337\r\n* chore: bump up @vitejs/plugin-vue version to v5 by @renovate in https://github.com/toeverything/AFFiNE/pull/5394\r\n* fix(core): correct title of onboarding article-2 by @CatsJuice in https://github.com/toeverything/AFFiNE/pull/5387\r\n* chore(core): add background color to questionnaire by @JimmFly in https://github.com/toeverything/AFFiNE/pull/5396\r\n* ci: define tag name to fix nightly release failing by @joooye34 in https://github.com/toeverything/AFFiNE/pull/5397\r\n* fix(core): workpace list blink issue on open by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5400\r\n* fix(core): about setting blink issue by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5399\r\n* fix(electron): set stable base url to app.affine.pro by @joooye34 in https://github.com/toeverything/AFFiNE/pull/5401\r\n* fix(core): trash page footer display issue by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5402\r\n* chore: bump up @types/supertest version to v6 by @renovate in https://github.com/toeverything/AFFiNE/pull/5376\r\n* chore: bump up react-i18next version to v14 by @renovate in https://github.com/toeverything/AFFiNE/pull/5375\r\n* fix(core): avatars are not aligned by @JimmFly in https://github.com/toeverything/AFFiNE/pull/5404\r\n* fix(infra): workaround for self-referencing in storybook by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5406\r\n\r\n\r\n**Full Changelog**: https://github.com/toeverything/AFFiNE/compare/v0.11.1-canary.0...v0.11.1-canary.1", - "mentions_count": 5 - }, - { - "url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135103520", - "assets_url": "https://api.github.com/repos/toeverything/AFFiNE/releases/135103520/assets", - "upload_url": "https://uploads.github.com/repos/toeverything/AFFiNE/releases/135103520/assets{?name,label}", - "html_url": "https://github.com/toeverything/AFFiNE/releases/tag/v0.11.1-canary.1", - "id": 135103520, - "node_id": "RE_kwDOHvxvHs4IDYQg", - "tag_name": "v0.11.1-canary.1", - "target_commitish": "canary", - "name": "0.11.1-canary.1", - "draft": false, - "prerelease": true, - "created_at": "2023-12-26T12:27:20Z", - "published_at": "2023-12-26T13:24:28Z", - "tarball_url": "https://api.github.com/repos/toeverything/AFFiNE/tarball/v0.11.1-canary.1", - "zipball_url": "https://api.github.com/repos/toeverything/AFFiNE/zipball/v0.11.1-canary.1", - "body": "## What's Changed\r\n* fix(core): remove plugins settings by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5337\r\n* chore: bump up @vitejs/plugin-vue version to v5 by @renovate in https://github.com/toeverything/AFFiNE/pull/5394\r\n* fix(core): correct title of onboarding article-2 by @CatsJuice in https://github.com/toeverything/AFFiNE/pull/5387\r\n* chore(core): add background color to questionnaire by @JimmFly in https://github.com/toeverything/AFFiNE/pull/5396\r\n* ci: define tag name to fix nightly release failing by @joooye34 in https://github.com/toeverything/AFFiNE/pull/5397\r\n* fix(core): workpace list blink issue on open by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5400\r\n* fix(core): about setting blink issue by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5399\r\n* fix(electron): set stable base url to app.affine.pro by @joooye34 in https://github.com/toeverything/AFFiNE/pull/5401\r\n* fix(core): trash page footer display issue by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5402\r\n* chore: bump up @types/supertest version to v6 by @renovate in https://github.com/toeverything/AFFiNE/pull/5376\r\n* chore: bump up react-i18next version to v14 by @renovate in https://github.com/toeverything/AFFiNE/pull/5375\r\n* fix(core): avatars are not aligned by @JimmFly in https://github.com/toeverything/AFFiNE/pull/5404\r\n* fix(infra): workaround for self-referencing in storybook by @pengx17 in https://github.com/toeverything/AFFiNE/pull/5406\r\n\r\n\r\n**Full Changelog**: https://github.com/toeverything/AFFiNE/compare/v0.11.1-canary.0...v0.11.1-canary.1", - "mentions_count": 5 - } -] diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-beta.1.txt b/packages/frontend/electron/test/main/fixtures/releases/0.11.1-beta.1.txt deleted file mode 100644 index fd9a7434cf04..000000000000 --- a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-beta.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.11.1-beta.1 -files: - - url: affine-beta-windows-x64.exe - sha512: uQdF7bEZteCMp/bT7vwCjlEcAf6osW9zZ+Q5grEkmbHPpcqCCzLudguXqHIwohO4GGq9pS8H4kJzG0LZc+SmXg== - size: 179515752 - - url: affine-beta-macos-arm64.dmg - sha512: gRsi4XO4+kREQuLX2CnS2V9vvUmBMmoGR6MvoB6TEFm1WiC8k8v69DRKYQ0Vjlom/j9HZlBEYUTqcW7IsMgrpw== - size: 169726061 - - url: affine-beta-macos-arm64.zip - sha512: +aXkjJfQnp2dUz3Y0i5cL6V5Hm1OkYVlwM/KAcZfLlvLoU3zQ0zSZvJ6+H2IlIhOebSq56Ip76H5NP8fe7UnOw== - size: 169007175 - - url: affine-beta-macos-x64.dmg - sha512: i5V95dLx3iWFpNj89wFU40THT3Oeow8g706Z6/mG1zYOIR3kXUkIpp6+wJmlfe9g4iwNmRd0rgI4HAG5LaQagg== - size: 175712730 - - url: affine-beta-macos-x64.zip - sha512: DnRUHcj+4FluII5kTbUuEAQI2CIRufd1Z0P98pwa/uX5hk2iOj1QzMD8WM+MTbFNC6rZvMtMlos8GyVLsZmK0w== - size: 175235583 -path: affine-beta-windows-x64.exe -sha512: uQdF7bEZteCMp/bT7vwCjlEcAf6osW9zZ+Q5grEkmbHPpcqCCzLudguXqHIwohO4GGq9pS8H4kJzG0LZc+SmXg== -releaseDate: 2023-12-27T08:59:31.826Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.1.txt b/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.1.txt deleted file mode 100644 index 36dc8f50c8e7..000000000000 --- a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.11.1-canary.1 -files: - - url: affine-canary-windows-x64.exe - sha512: qbK4N6+axVO2dA/iPzfhANWxCZXY1S3ci9qYIT1v/h0oCjc6vqpXU+2KRGL5mplL6wmVgJAOpqrfnq9gHMsfDg== - size: 179526504 - - url: affine-canary-macos-arm64.dmg - sha512: ++LAGuxTmFAVd65k8UpKKfU19iisvXHKDDfPkGlTVC000QP3foeS21BmTgYnM1ZuhEC6KGzSGrqvUDVDNYnRmA== - size: 169903530 - - url: affine-canary-macos-arm64.zip - sha512: IAWbCpVqPPVVzDowGKGnKZzHN2jPgAW40v+bUZR2tdgDrqIAVy4YdamYz8WmEwpg1TXmi0ueSsWgGFPgBIr0iA== - size: 169085665 - - url: affine-canary-macos-x64.dmg - sha512: 4y4/KkmkmFmZ94ntRAN0lSX7aZzgEd4Wg7f85Tff296P3x85sbPF4FFIp++Zx/cgBZBUQwMWe9xeGlefompQ/g== - size: 175920978 - - url: affine-canary-macos-x64.zip - sha512: S1MuMHooMOQ9eJ+coRYmyz6k5lnWIMqHotSrywxGGo7sFXBY+O5F4PeKgNREJtwXjAIxv0GxZVvbe5jc+onw9w== - size: 175315484 -path: affine-canary-windows-x64.exe -sha512: qbK4N6+axVO2dA/iPzfhANWxCZXY1S3ci9qYIT1v/h0oCjc6vqpXU+2KRGL5mplL6wmVgJAOpqrfnq9gHMsfDg== -releaseDate: 2023-12-26T13:24:28.221Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.2.txt b/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.2.txt deleted file mode 100644 index fe8f4de82da5..000000000000 --- a/packages/frontend/electron/test/main/fixtures/releases/0.11.1-canary.2.txt +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.11.1-canary.2 -files: - - url: affine-canary-windows-x64.exe - sha512: qbK4N6+axVO2dA/iPzfhANWxCZXY1S3ci9qYIT1v/h0oCjc6vqpXU+2KRGL5mplL6wmVgJAOpqrfnq9gHMsfDg== - size: 179526504 - - url: affine-canary-macos-arm64.dmg - sha512: ++LAGuxTmFAVd65k8UpKKfU19iisvXHKDDfPkGlTVC000QP3foeS21BmTgYnM1ZuhEC6KGzSGrqvUDVDNYnRmA== - size: 169903530 - - url: affine-canary-macos-arm64.zip - sha512: IAWbCpVqPPVVzDowGKGnKZzHN2jPgAW40v+bUZR2tdgDrqIAVy4YdamYz8WmEwpg1TXmi0ueSsWgGFPgBIr0iA== - size: 169085665 - - url: affine-canary-macos-x64.dmg - sha512: 4y4/KkmkmFmZ94ntRAN0lSX7aZzgEd4Wg7f85Tff296P3x85sbPF4FFIp++Zx/cgBZBUQwMWe9xeGlefompQ/g== - size: 175920978 - - url: affine-canary-macos-x64.zip - sha512: S1MuMHooMOQ9eJ+coRYmyz6k5lnWIMqHotSrywxGGo7sFXBY+O5F4PeKgNREJtwXjAIxv0GxZVvbe5jc+onw9w== - size: 175315484 -path: affine-canary-windows-x64.exe -sha512: qbK4N6+axVO2dA/iPzfhANWxCZXY1S3ci9qYIT1v/h0oCjc6vqpXU+2KRGL5mplL6wmVgJAOpqrfnq9gHMsfDg== -releaseDate: 2023-12-26T13:24:28.221Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.11.1.txt b/packages/frontend/electron/test/main/fixtures/releases/0.11.1.txt deleted file mode 100644 index 843b63228788..000000000000 --- a/packages/frontend/electron/test/main/fixtures/releases/0.11.1.txt +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.11.1 -files: - - url: affine-stable-windows-x64.exe - sha512: qHRO31Fb8F+Q/hiGiJJ2WH+PpSC5iUIPtWujUoI+XNMz7UfhCGxoVW9U38CTE9LecILS119SZN0rrHkmu+nQiw== - size: 179504488 - - url: affine-stable-macos-arm64.dmg - sha512: uDS7bZusoU5p2t4bi1k/IdvChj3BRIWbOLanbhAfIjwBmf9FM3553wgeUzQLRMRuD5wavsw/aA1BaqFJIbwkyQ== - size: 169793193 - - url: affine-stable-macos-arm64.zip - sha512: n4CrOgNPd70WqPfe0ZEKzmyOdqOlVnFvQqylIlt92eqKrUb8jcxVThQY+GU2Jy3jVjvKqUvubodDbIkQhXQ1xQ== - size: 169014676 - - url: affine-stable-macos-x64.dmg - sha512: xFy1kt1025h1wqBjHt+IoPweC40UqAZvZLI2XD3LIlPG60jZ03+QM75UwaXYuVYEqe3kO9a3WeFcrfGdoj4Hzw== - size: 175720872 - - url: affine-stable-macos-x64.zip - sha512: FfgX22ytleb8fv35odzhDyFsmiUVPdI4XQjTB4uDmkhQ719i+W4yctUnP5TSCpymYC0HgVRFSVg4Jkuuli+8ug== - size: 175244411 -path: affine-stable-windows-x64.exe -sha512: qHRO31Fb8F+Q/hiGiJJ2WH+PpSC5iUIPtWujUoI+XNMz7UfhCGxoVW9U38CTE9LecILS119SZN0rrHkmu+nQiw== -releaseDate: 2023-12-27T11:04:53.014Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-linux.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-linux.yml new file mode 100644 index 000000000000..5c573e1bd3e7 --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-linux.yml @@ -0,0 +1,11 @@ +version: 0.16.3-beta.2 +files: + - url: affine-0.16.3-beta.2-beta-linux-x64.appimage + sha512: munCzfD0tOky2MRZVVu+/JCCMrQ4C/EcOSxkNXrEVwK0aoeYo3Q0H1Cm/KJ+7mZ2yMfxctdPH0KGfHRL0tNENg== + size: 178308288 + - url: affine-0.16.3-beta.2-beta-linux-x64.zip + sha512: 6emy8f8QrhrAdNxOfHj9PSbWtRXI/LocvpsckrbqrYIi7sU12GmmS0dI2SCxvDBwYTDSoC4mh0erfzUeDSLAEg== + size: 176402697 +path: affine-0.16.3-beta.2-beta-linux-x64.appimage +sha512: munCzfD0tOky2MRZVVu+/JCCMrQ4C/EcOSxkNXrEVwK0aoeYo3Q0H1Cm/KJ+7mZ2yMfxctdPH0KGfHRL0tNENg== +releaseDate: 2024-08-14T06:56:24.609Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-mac.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-mac.yml new file mode 100644 index 000000000000..e236f7ee60fe --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest-mac.yml @@ -0,0 +1,17 @@ +version: 0.16.3-beta.2 +files: + - url: affine-0.16.3-beta.2-beta-macos-arm64.dmg + sha512: 5sR2TXAV+hgMOvplG2CobW5VSsrGXsAoZmgWqs7uEpf491XoYkUFl5iXQZaL23xi0HpuGkSS33jSZ/b7pwy0Hg== + size: 168063426 + - url: affine-0.16.3-beta.2-beta-macos-arm64.zip + sha512: aI/q3DgyORjCNLxrtsT7W1KoAcuaxUT0AY0QtwnRjtMYiJYV/s4aTQtGFISDEdHcU9Vy+mS8ZcZ6yRVCksdKAg== + size: 167528680 + - url: affine-0.16.3-beta.2-beta-macos-x64.dmg + sha512: P8JoSRP5tu2fpQb/EwdV6OSPNx9lEj1NT8iAZhawgeCVLaWokMMVkcsqyRYi7vX25Hf7eegvxF5LNpYzUsfKQQ== + size: 175049454 + - url: affine-0.16.3-beta.2-beta-macos-x64.zip + sha512: gpzlGq0ucZUeJOZi6h/cIFRpvIhGJ5qYmWkrD6ncMgSJQOVWQapfFOrQrmU7SCwtE92pfRUcrZeH3g9cNrJDSQ== + size: 174740492 +path: affine-0.16.3-beta.2-beta-macos-arm64.dmg +sha512: 5sR2TXAV+hgMOvplG2CobW5VSsrGXsAoZmgWqs7uEpf491XoYkUFl5iXQZaL23xi0HpuGkSS33jSZ/b7pwy0Hg== +releaseDate: 2024-08-14T06:56:23.981Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest.yml new file mode 100644 index 000000000000..6c460a106a1e --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3-beta.2/latest.yml @@ -0,0 +1,11 @@ +version: 0.16.3-beta.2 +files: + - url: affine-0.16.3-beta.2-beta-windows-x64.exe + sha512: iHiIF5Swrgz6RfL/Xf9KFZsmFggGFWtSrkj7IUJUt69Z+gxQQp2dO4wrQ4uaZmiqMVr+8Op++oeczMhcjXGPHw== + size: 177771240 + - url: affine-0.16.3-beta.2-beta-windows-x64.nsis.exe + sha512: 5437aZddjbgzwGWsj/nxgepS2jBM/WB561DNz3XXA5sTtqPVafMvEmcbE1KE86JZTpAAJHcJheSwjxdYOL/Lgw== + size: 130309240 +path: affine-0.16.3-beta.2-beta-windows-x64.exe +sha512: iHiIF5Swrgz6RfL/Xf9KFZsmFggGFWtSrkj7IUJUt69Z+gxQQp2dO4wrQ4uaZmiqMVr+8Op++oeczMhcjXGPHw== +releaseDate: 2024-08-14T06:56:22.750Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-linux.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-linux.yml new file mode 100644 index 000000000000..b16b9357a956 --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-linux.yml @@ -0,0 +1,11 @@ +version: 0.16.3 +files: + - url: affine-0.16.3-stable-linux-x64.appimage + sha512: nmID71T7jq9yKCdujVUeL71TLXmwIdaaWZB0ouDX13Np1vahS1+1A5uJbHUzTH0N/sN0W+LKUg9L29wNgi42gw== + size: 178308288 + - url: affine-0.16.3-stable-linux-x64.zip + sha512: fsHTT0fUeU/uLGdlRiuddzSuJWIOcaUTgUj7DB5XSQJ4qA5blAcpij8zOil0ww3Ea7Kwe7qcIe4SSCtNFu31sQ== + size: 176405078 +path: affine-0.16.3-stable-linux-x64.appimage +sha512: nmID71T7jq9yKCdujVUeL71TLXmwIdaaWZB0ouDX13Np1vahS1+1A5uJbHUzTH0N/sN0W+LKUg9L29wNgi42gw== +releaseDate: 2024-08-14T07:11:42.171Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-mac.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-mac.yml new file mode 100644 index 000000000000..2c28db0d7e6f --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest-mac.yml @@ -0,0 +1,17 @@ +version: 0.16.3 +files: + - url: affine-0.16.3-stable-macos-arm64.dmg + sha512: fmJWpi45gVYYUavb0Cd6Y9DR2nxBc3wMagHOiMF1PPg+4tEyHGmVIhRIwY/QaJ5TAR+3tRAENZwen2gvja0UtQ== + size: 168093091 + - url: affine-0.16.3-stable-macos-arm64.zip + sha512: u1ud8pJ613A5Oqh3fbcnUUOA4hNoURWBdtAMJoeZ6EIAUvZzV0tsDcAqLiEP89LKbitaH0IdrW3D8EFSsZ9kRw== + size: 167540517 + - url: affine-0.16.3-stable-macos-x64.dmg + sha512: Ou1W6/xHyM+ZN9BLYvc+8qCB8wR9F3jLQP5m3oG0uIDDw7wwoR+ny3gcWbDzalfxoOR84CvM74LIfc7BQf69Uw== + size: 175029125 + - url: affine-0.16.3-stable-macos-x64.zip + sha512: oot098M9qqdRbw+znnuLjVedZ1U59p4m+gzSxRtpCuYdfvumvu5/RN1jvY2cHssqstJj/Ybh4eBTlREZMgKyyg== + size: 174752343 +path: affine-0.16.3-stable-macos-arm64.dmg +sha512: fmJWpi45gVYYUavb0Cd6Y9DR2nxBc3wMagHOiMF1PPg+4tEyHGmVIhRIwY/QaJ5TAR+3tRAENZwen2gvja0UtQ== +releaseDate: 2024-08-14T07:11:41.503Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest.yml b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest.yml new file mode 100644 index 000000000000..2efc8b99e7cf --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.16.3/latest.yml @@ -0,0 +1,11 @@ +version: 0.16.3 +files: + - url: affine-0.16.3-stable-windows-x64.exe + sha512: 47zaLkAhSxPuWsKq01dSEt8GusXqK1rmSaiOTBLe32lmUiXPhUqYO5JhzbrjJKx7/TFcic4UDJ/Zir3wf9fKRA== + size: 177757416 + - url: affine-0.16.3-stable-windows-x64.nsis.exe + sha512: G3Rxa3onqlJTGQIcz7Rz6ZQ/6rAwjzjYnW/HB5yzXkjN6e5yfW2JBk765+AyiPFV5Mn4Rloj7V6GM6m4q7WfWg== + size: 130302976 +path: affine-0.16.3-stable-windows-x64.exe +sha512: 47zaLkAhSxPuWsKq01dSEt8GusXqK1rmSaiOTBLe32lmUiXPhUqYO5JhzbrjJKx7/TFcic4UDJ/Zir3wf9fKRA== +releaseDate: 2024-08-14T07:11:40.285Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-linux.yml b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-linux.yml new file mode 100644 index 000000000000..05e7e913999c --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-linux.yml @@ -0,0 +1,11 @@ +version: 0.17.0-canary.7 +files: + - url: affine-0.17.0-canary.7-canary-linux-x64.appimage + sha512: qspZkDlItrHu02vSItbjc3I+t4FcOiHOzGt0Ap6IeZEFKal+hoOh4WIcUN16dlS/OoFm+is8yPBHqN/70xhWKA== + size: 181990592 + - url: affine-0.17.0-canary.7-canary-linux-x64.zip + sha512: fom2iuMiPUlnHAGJhQdAnWJwMggK4rloNkiWqH8ZHF1Q09oturgSMGgkUEWZWXsZPpORt545eYNv5Zg9aff8yQ== + size: 180105256 +path: affine-0.17.0-canary.7-canary-linux-x64.appimage +sha512: qspZkDlItrHu02vSItbjc3I+t4FcOiHOzGt0Ap6IeZEFKal+hoOh4WIcUN16dlS/OoFm+is8yPBHqN/70xhWKA== +releaseDate: 2024-08-29T08:20:53.453Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-mac.yml b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-mac.yml new file mode 100644 index 000000000000..c3137c83c1a3 --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest-mac.yml @@ -0,0 +1,17 @@ +version: 0.17.0-canary.7 +files: + - url: affine-0.17.0-canary.7-canary-macos-arm64.dmg + sha512: Tdy7dgrCHP95PjsZBt1evxUk7DUkn+JpseBQj1Gz60MmcsFx+0NtJvofZbUcsLFiS0IC32JM/szHlHiNGEznrQ== + size: 170556866 + - url: affine-0.17.0-canary.7-canary-macos-arm64.zip + sha512: pmYD0B5Z9hrzgjcHmRCKnNawoPJiO5r1RjBBZi+THVL3TyKXzpJBr9HTNQkjYnQYgqHX4q2eoONsDNCIoqTeBA== + size: 170382513 + - url: affine-0.17.0-canary.7-canary-macos-x64.dmg + sha512: k4a4GUmy/6MmSc1xVGJNeNCCtYylWWSRcfDoZA+syUhZFY6x3xrOft972ONsiRrJukXWlKrFmVTwoW68Ywe49A== + size: 176815834 + - url: affine-0.17.0-canary.7-canary-macos-x64.zip + sha512: PL24krtjeiQY53F7OuS+hh8EZP3YpbLle0JboXiddSrulypxzBRquOCCinNW88Kg8ZJbOrfTkxaNOHpOAVfeaQ== + size: 176948223 +path: affine-0.17.0-canary.7-canary-macos-arm64.dmg +sha512: Tdy7dgrCHP95PjsZBt1evxUk7DUkn+JpseBQj1Gz60MmcsFx+0NtJvofZbUcsLFiS0IC32JM/szHlHiNGEznrQ== +releaseDate: 2024-08-29T08:20:52.810Z diff --git a/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest.yml b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest.yml new file mode 100644 index 000000000000..1353b8747225 --- /dev/null +++ b/packages/frontend/electron/test/main/fixtures/releases/0.17.0-canary.7/latest.yml @@ -0,0 +1,11 @@ +version: 0.17.0-canary.7 +files: + - url: affine-0.17.0-canary.7-canary-windows-x64.exe + sha512: cF47Wcu69PXyMVswSzrdNktNO2lqkjsyJ/HQr2qWjFPuIJfcad9QDTfOyCVsMCV6KGUSSeFiTHyObWgKd6z2DQ== + size: 182557416 + - url: affine-0.17.0-canary.7-canary-windows-x64.nsis.exe + sha512: ztugqKwPpxDDSK1OpzUPkGvL8wLXwg9rh985bs9ZvxydY037yKBAZOk96PPtow2qqRb5/9Xn8MuGrWgchqXkVg== + size: 133493672 +path: affine-0.17.0-canary.7-canary-windows-x64.exe +sha512: cF47Wcu69PXyMVswSzrdNktNO2lqkjsyJ/HQr2qWjFPuIJfcad9QDTfOyCVsMCV6KGUSSeFiTHyObWgKd6z2DQ== +releaseDate: 2024-08-29T08:20:51.573Z diff --git a/packages/frontend/electron/test/main/updater.spec.ts b/packages/frontend/electron/test/main/updater.spec.ts index 02e24b9d2b83..bd9fd70a00b6 100644 --- a/packages/frontend/electron/test/main/updater.spec.ts +++ b/packages/frontend/electron/test/main/updater.spec.ts @@ -1,4 +1,4 @@ -import nodePath from 'node:path'; +import path from 'node:path'; import { fileURLToPath } from 'node:url'; import type { UpdateCheckResult } from 'electron-updater'; @@ -16,7 +16,7 @@ import { vi, } from 'vitest'; -import { CustomGitHubProvider } from '../../src/main/updater/custom-github-provider'; +import { AFFiNEUpdateProvider } from '../../src/main/updater/affine-update-provider'; import { MockedAppAdapter, MockedUpdater } from './mocks'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -39,72 +39,52 @@ const platformTail = (() => { } })(); -function response404() { - return HttpResponse.text('Not Found', { status: 404 }); -} -function response403() { - return HttpResponse.text('403', { status: 403 }); -} - describe('testing for client update', () => { const expectReleaseList = [ - { buildType: 'stable', version: '0.11.1' }, - { buildType: 'beta', version: '0.11.1-beta.1' }, - { buildType: 'canary', version: '0.11.1-canary.1' }, + { buildType: 'stable', version: '0.16.3' }, + { buildType: 'beta', version: '0.16.3-beta.2' }, + { buildType: 'canary', version: '0.17.0-canary.7' }, ]; const basicRequestHandlers = [ - http.get( - 'https://github.com/toeverything/AFFiNE/releases.atom', - async () => { - const buffer = await fs.readFile( - nodePath.join(__dirname, 'fixtures', 'feeds.txt') - ); - const content = buffer.toString(); - return HttpResponse.xml(content); - } - ), + http.get('https://affine.pro/api/worker/releases', async ({ request }) => { + const url = new URL(request.url); + const buffer = await fs.readFile( + path.join( + __dirname, + 'fixtures', + 'candidates', + `${url.searchParams.get('channel')}.json` + ) + ); + const content = buffer.toString(); + return HttpResponse.text(content); + }), ...flatten( - expectReleaseList.map(({ version, buildType }) => { + expectReleaseList.map(({ version }) => { return [ http.get( `https://github.com/toeverything/AFFiNE/releases/download/v${version}/latest${platformTail}.yml`, - async () => { + async req => { const buffer = await fs.readFile( - nodePath.join( + path.join( __dirname, 'fixtures', 'releases', - `${version}.txt` + version, + path.parse(req.request.url).base ) ); const content = buffer.toString(); return HttpResponse.text(content); } ), - http.get( - `https://github.com/toeverything/AFFiNE/releases/download/v${version}/${buildType}${platformTail}.yml`, - response404 - ), ]; }) ), ]; - describe('release api request successfully', () => { - const server = setupServer( - ...basicRequestHandlers, - http.get( - 'https://api.github.com/repos/toeverything/affine/releases', - async () => { - const buffer = await fs.readFile( - nodePath.join(__dirname, 'fixtures', 'release-list.txt') - ); - const content = buffer.toString(); - return HttpResponse.xml(content); - } - ) - ); + const server = setupServer(...basicRequestHandlers); beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterAll(() => server.close()); afterEach(() => server.resetHandlers()); @@ -113,80 +93,19 @@ describe('testing for client update', () => { it(`check update for ${buildType} channel successfully`, async () => { const app = new MockedAppAdapter('0.10.0'); const updater = new MockedUpdater(null, app); - updater.allowPrerelease = buildType !== 'stable'; - - const feedUrl: Parameters[0] = { - channel: buildType, - // hack for custom provider - provider: 'custom' as 'github', - repo: 'AFFiNE', - owner: 'toeverything', - releaseType: buildType === 'stable' ? 'release' : 'prerelease', - // @ts-expect-error hack for custom provider - updateProvider: CustomGitHubProvider, - }; - updater.setFeedURL(feedUrl); + updater.setFeedURL( + AFFiNEUpdateProvider.configFeed({ + channel: buildType as any, + }) + ); const info = (await updater.checkForUpdates()) as UpdateCheckResult; expect(info).not.toBe(null); expect(info.updateInfo.releaseName).toBe(version); expect(info.updateInfo.version).toBe(version); - expect(info.updateInfo.releaseNotes?.length).toBeGreaterThan(0); + // expect(info.updateInfo.releaseNotes?.length).toBeGreaterThan(0); }); } }); - - describe('release api request limited', () => { - const server = setupServer( - ...basicRequestHandlers, - http.get( - 'https://api.github.com/repos/toeverything/affine/releases', - response403 - ), - http.get( - `https://github.com/toeverything/AFFiNE/releases/download/v0.11.1-canary.2/canary${platformTail}.yml`, - async () => { - const buffer = await fs.readFile( - nodePath.join( - __dirname, - 'fixtures', - 'releases', - `0.11.1-canary.2.txt` - ) - ); - const content = buffer.toString(); - return HttpResponse.text(content); - } - ) - ); - beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); - afterAll(() => server.close()); - afterEach(() => server.resetHandlers()); - - it('check update for canary channel get v0.11.1-canary.2', async () => { - const app = new MockedAppAdapter('0.10.0'); - const updater = new MockedUpdater(null, app); - updater.allowPrerelease = true; - - const feedUrl: Parameters[0] = { - channel: 'canary', - // hack for custom provider - provider: 'custom' as 'github', - repo: 'AFFiNE', - owner: 'toeverything', - releaseType: 'prerelease', - // @ts-expect-error hack for custom provider - updateProvider: CustomGitHubProvider, - }; - - updater.setFeedURL(feedUrl); - - const info = (await updater.checkForUpdates()) as UpdateCheckResult; - expect(info).not.toBe(null); - expect(info.updateInfo.releaseName).toBe('0.11.1-canary.2'); - expect(info.updateInfo.version).toBe('0.11.1-canary.2'); - expect(info.updateInfo.releaseNotes?.length).toBe(0); - }); - }); }); diff --git a/packages/frontend/graphql/src/graphql/get-workspace-page-meta.gql b/packages/frontend/graphql/src/graphql/get-workspace-page-meta.gql new file mode 100644 index 000000000000..1c047d5521a6 --- /dev/null +++ b/packages/frontend/graphql/src/graphql/get-workspace-page-meta.gql @@ -0,0 +1,16 @@ +query getWorkspacePageMetaById($id: String!, $pageId: String!) { + workspace(id: $id) { + pageMeta(pageId: $pageId) { + createdAt + updatedAt + createdBy { + name + avatarUrl + } + updatedBy { + name + avatarUrl + } + } + } +} diff --git a/packages/frontend/graphql/src/graphql/histories.gql b/packages/frontend/graphql/src/graphql/histories.gql index b6aade3e0cfc..90eef6ab86d1 100644 --- a/packages/frontend/graphql/src/graphql/histories.gql +++ b/packages/frontend/graphql/src/graphql/histories.gql @@ -8,6 +8,10 @@ query listHistory( histories(guid: $pageDocId, take: $take, before: $before) { id timestamp + editor { + name + avatarUrl + } } } } diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts index 9ffcaeccd9a0..ef12a94e5db6 100644 --- a/packages/frontend/graphql/src/graphql/index.ts +++ b/packages/frontend/graphql/src/graphql/index.ts @@ -610,6 +610,30 @@ query getWorkspaceFeatures($workspaceId: String!) { }`, }; +export const getWorkspacePageMetaByIdQuery = { + id: 'getWorkspacePageMetaByIdQuery' as const, + operationName: 'getWorkspacePageMetaById', + definitionName: 'workspace', + containsFile: false, + query: ` +query getWorkspacePageMetaById($id: String!, $pageId: String!) { + workspace(id: $id) { + pageMeta(pageId: $pageId) { + createdAt + updatedAt + createdBy { + name + avatarUrl + } + updatedBy { + name + avatarUrl + } + } + } +}`, +}; + export const getWorkspacePublicByIdQuery = { id: 'getWorkspacePublicByIdQuery' as const, operationName: 'getWorkspacePublicById', @@ -696,6 +720,10 @@ query listHistory($workspaceId: String!, $pageDocId: String!, $take: Int, $befor histories(guid: $pageDocId, take: $take, before: $before) { id timestamp + editor { + name + avatarUrl + } } } }`, diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index 3aa326ddc5fb..3efd417d9ada 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -238,6 +238,7 @@ export interface DocHistoryNotFoundDataType { export interface DocHistoryType { __typename?: 'DocHistoryType'; + editor: Maybe; id: Scalars['String']['output']; timestamp: Scalars['DateTime']['output']; workspaceId: Scalars['String']['output']; @@ -249,6 +250,12 @@ export interface DocNotFoundDataType { spaceId: Scalars['String']['output']; } +export interface EditorType { + __typename?: 'EditorType'; + avatarUrl: Maybe; + name: Scalars['String']['output']; +} + export type ErrorDataUnion = | AlreadyInSpaceDataType | BlobNotFoundDataType @@ -1189,6 +1196,14 @@ export interface WorkspacePage { workspaceId: Scalars['String']['output']; } +export interface WorkspacePageMeta { + __typename?: 'WorkspacePageMeta'; + createdAt: Scalars['DateTime']['output']; + createdBy: Maybe; + updatedAt: Scalars['DateTime']['output']; + updatedBy: Maybe; +} + export interface WorkspaceType { __typename?: 'WorkspaceType'; /** Available features of workspace */ @@ -1211,6 +1226,8 @@ export interface WorkspaceType { members: Array; /** Owner of workspace */ owner: UserType; + /** Cloud page metadata of workspace */ + pageMeta: WorkspacePageMeta; /** Permission of current signed in user in workspace */ permission: Permission; /** is Public workspace */ @@ -1239,6 +1256,10 @@ export interface WorkspaceTypeMembersArgs { take: InputMaybe; } +export interface WorkspaceTypePageMetaArgs { + pageId: Scalars['String']['input']; +} + export interface WorkspaceTypePublicPageArgs { pageId: Scalars['String']['input']; } @@ -1787,6 +1808,33 @@ export type GetWorkspaceFeaturesQuery = { workspace: { __typename?: 'WorkspaceType'; features: Array }; }; +export type GetWorkspacePageMetaByIdQueryVariables = Exact<{ + id: Scalars['String']['input']; + pageId: Scalars['String']['input']; +}>; + +export type GetWorkspacePageMetaByIdQuery = { + __typename?: 'Query'; + workspace: { + __typename?: 'WorkspaceType'; + pageMeta: { + __typename?: 'WorkspacePageMeta'; + createdAt: string; + updatedAt: string; + createdBy: { + __typename?: 'EditorType'; + name: string; + avatarUrl: string | null; + } | null; + updatedBy: { + __typename?: 'EditorType'; + name: string; + avatarUrl: string | null; + } | null; + }; + }; +}; + export type GetWorkspacePublicByIdQueryVariables = Exact<{ id: Scalars['String']['input']; }>; @@ -1865,6 +1913,11 @@ export type ListHistoryQuery = { __typename?: 'DocHistoryType'; id: string; timestamp: string; + editor: { + __typename?: 'EditorType'; + name: string; + avatarUrl: string | null; + } | null; }>; }; }; @@ -2487,6 +2540,11 @@ export type Queries = variables: GetWorkspaceFeaturesQueryVariables; response: GetWorkspaceFeaturesQuery; } + | { + name: 'getWorkspacePageMetaByIdQuery'; + variables: GetWorkspacePageMetaByIdQueryVariables; + response: GetWorkspacePageMetaByIdQuery; + } | { name: 'getWorkspacePublicByIdQuery'; variables: GetWorkspacePublicByIdQueryVariables; diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 26c5d757969c..f129013d448a 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -118,7 +118,7 @@ "Export": "Export", "Export AFFiNE backup file": "Export AFFiNE backup file", "Export Description": "You can export the entire Workspace data for backup, and the exported data can be re-imported.", - "Export Shared Pages Description": "Download a static copy of your page to share with others.", + "Export Shared Pages Description": "Download a static copy of your page to share with others", "Export Workspace": "Export Workspace <1>{{workspace}} is coming soon", "Export failed": "Export failed", "Export success": "Export success", @@ -858,6 +858,8 @@ "com.affine.page-properties.page-info": "Info", "com.affine.page-properties.page-info.view": "View Info", "com.affine.page-properties.property-value-placeholder": "Empty", + "com.affine.page-properties.property-user-avatar-no-record": "No Record", + "com.affine.page-properties.property-user-local": "Local User", "com.affine.page-properties.property.always-hide": "Always hide", "com.affine.page-properties.property.always-show": "Always show", "com.affine.page-properties.property.checkbox": "Checkbox", @@ -872,6 +874,9 @@ "com.affine.page-properties.property.show-in-view": "Show in view", "com.affine.page-properties.property.tags": "Tags", "com.affine.page-properties.property.text": "Text", + "com.affine.page-properties.property.createdBy": "Created by", + "com.affine.page-properties.property.updatedBy": "Last edited by", + "com.affine.page-properties.local-user": "Local user", "com.affine.page-properties.settings.title": "customize properties", "com.affine.page-properties.tags.open-tags-page": "Open tag page", "com.affine.page-properties.tags.selector-header-title": "Select tag or create one", @@ -1340,7 +1345,7 @@ "com.affine.settings.workspace.experimental-features": "Experimental features", "com.affine.settings.workspace.experimental-features.get-started": "Get started", "com.affine.settings.workspace.experimental-features.header.plugins": "Experimental features", - "com.affine.settings.workspace.experimental-features.header.subtitle": "You can customize your workspace here.", + "com.affine.settings.workspace.experimental-features.header.subtitle": "Some features available for early access", "com.affine.settings.workspace.experimental-features.prompt-disclaimer": "I am aware of the risks, and I am willing to continue to use it.", "com.affine.settings.workspace.experimental-features.prompt-header": "Do you want to use the plugin system that is in an experimental stage?", "com.affine.settings.workspace.experimental-features.prompt-warning": "You are about to enable an experimental feature. This feature is still in development and may contain errors or behave unpredictably. Please proceed with caution and at your own risk.", @@ -1356,6 +1361,7 @@ "com.affine.settings.workspace.properties.doc_others": "<0>{{count}} docs", "com.affine.settings.workspace.properties.edit-property": "Edit property", "com.affine.settings.workspace.properties.general-properties": "General properties", + "com.affine.settings.workspace.properties.readonly-properties": "Readonly properties", "com.affine.settings.workspace.properties.header.subtitle": "Manage workspace <1>{{name}} properties", "com.affine.settings.workspace.properties.header.title": "Properties", "com.affine.settings.workspace.properties.in-use": "In use", @@ -1368,19 +1374,19 @@ "com.affine.share-menu.ShareMode": "Share mode", "com.affine.share-menu.SharePage": "Share doc", "com.affine.share-menu.ShareViaExport": "Share via export", - "com.affine.share-menu.ShareViaExportDescription": "Download a static copy of your doc to share with others.", - "com.affine.share-menu.ShareViaPrintDescription": "Print a paper copy.", + "com.affine.share-menu.ShareViaExportDescription": "Download a static copy of your doc to share with others", + "com.affine.share-menu.ShareViaPrintDescription": "Print a paper copy", "com.affine.share-menu.ShareWithLink": "Share with link", "com.affine.share-menu.ShareWithLinkDescription": "Create a link you can easily share with anyone. The visitors will open your doc in the form od a document", "com.affine.share-menu.SharedPage": "Shared doc", "com.affine.share-menu.option.link.label": "Anyone with the link", "com.affine.share-menu.option.link.readonly": "Read Only", - "com.affine.share-menu.option.link.readonly.description": "Anyone can access this link.", + "com.affine.share-menu.option.link.readonly.description": "Anyone can access this link", "com.affine.share-menu.option.link.no-access": "No Access", - "com.affine.share-menu.option.link.no-access.description": "Only workspace members can access this link.", - "com.affine.share-menu.option.permission.label": "Members in Workspace", + "com.affine.share-menu.option.link.no-access.description": "Only workspace members can access this link", + "com.affine.share-menu.option.permission.label": "Members in workspace", "com.affine.share-menu.option.permission.can-edit": "Can Edit", - "com.affine.share-menu.navigate.workspace": "Manage Workspace Members", + "com.affine.share-menu.navigate.workspace": "Manage workspace members", "com.affine.share-menu.copy": "Copy Link", "com.affine.share-menu.copy.page": "Copy Link to Page Mode", "com.affine.share-menu.copy.edgeless": "Copy Link to Edgeless Mode", diff --git a/tests/affine-local/e2e/doc-info-modal.spec.ts b/tests/affine-local/e2e/doc-info-modal.spec.ts index 78fec7a5337e..94edbe9a8dfa 100644 --- a/tests/affine-local/e2e/doc-info-modal.spec.ts +++ b/tests/affine-local/e2e/doc-info-modal.spec.ts @@ -137,4 +137,6 @@ test('add custom property', async ({ page }) => { await addCustomProperty(page, 'Number'); await addCustomProperty(page, 'Date'); await addCustomProperty(page, 'Checkbox'); + await addCustomProperty(page, 'Created by'); + await addCustomProperty(page, 'Last edited by'); }); diff --git a/tests/affine-local/e2e/page-properties.spec.ts b/tests/affine-local/e2e/page-properties.spec.ts index d96fb3cbcb1d..959fcbf8570f 100644 --- a/tests/affine-local/e2e/page-properties.spec.ts +++ b/tests/affine-local/e2e/page-properties.spec.ts @@ -85,6 +85,8 @@ test('add custom property', async ({ page }) => { await addCustomProperty(page, 'Number'); await addCustomProperty(page, 'Date'); await addCustomProperty(page, 'Checkbox'); + await addCustomProperty(page, 'Created by'); + await addCustomProperty(page, 'Last edited by'); }); test('add custom property & edit', async ({ page }) => { @@ -103,6 +105,8 @@ test('property table reordering', async ({ page }) => { await addCustomProperty(page, 'Number'); await addCustomProperty(page, 'Date'); await addCustomProperty(page, 'Checkbox'); + await addCustomProperty(page, 'Created by'); + await addCustomProperty(page, 'Last edited by'); await dragTo( page, @@ -119,6 +123,8 @@ test('property table reordering', async ({ page }) => { 'Date', 'Checkbox', 'Text', + 'Created by', + 'Last edited by', ].entries()) { await expect( page @@ -141,6 +147,8 @@ test('page info show more will show all properties', async ({ page }) => { await addCustomProperty(page, 'Number'); await addCustomProperty(page, 'Date'); await addCustomProperty(page, 'Checkbox'); + await addCustomProperty(page, 'Created by'); + await addCustomProperty(page, 'Last edited by'); await expect(page.getByTestId('page-info-show-more')).toBeVisible(); await page.click('[data-testid="page-info-show-more"]'); @@ -156,6 +164,8 @@ test('page info show more will show all properties', async ({ page }) => { 'Number', 'Date', 'Checkbox', + 'Created by', + 'Last edited by', ].entries()) { await expect( page