mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
refactor(core): move infra modules to core (#9207)
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import {
|
||||
type GlobalState,
|
||||
type Memento,
|
||||
wrapMemento,
|
||||
} from '@toeverything/infra';
|
||||
import { type Memento, wrapMemento } from '@toeverything/infra';
|
||||
|
||||
import type { GlobalState } from '../../storage';
|
||||
import type { AppSidebarState } from '../providers/storage';
|
||||
|
||||
export class AppSidebarStateImpl implements AppSidebarState {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type Framework, GlobalState } from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalState } from '../storage';
|
||||
import { AppSidebar } from './entities/app-sidebar';
|
||||
import { AppSidebarStateImpl } from './impls/storage';
|
||||
import { AppSidebarState } from './providers/storage';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GlobalState, Service } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { GlobalState } from '../../storage';
|
||||
import { AppSidebar } from '../entities/app-sidebar';
|
||||
|
||||
export class AppSidebarService extends Service {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { isNewTabTrigger } from '@affine/core/utils';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import { type MouseEvent, useCallback } from 'react';
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
useLiveData,
|
||||
useService,
|
||||
useServiceOptional,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { debounce } from 'lodash-es';
|
||||
import type { PropsWithChildren, ReactElement } from 'react';
|
||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { WorkspaceService } from '../../workspace';
|
||||
import { AppSidebarService } from '../services/app-sidebar';
|
||||
import * as styles from './fallback.css';
|
||||
import {
|
||||
@@ -285,7 +285,7 @@ const RandomBars = ({ count, header }: { count: number; header?: boolean }) => {
|
||||
/>
|
||||
) : null}
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
// oxlint-disable-next-line eslint-plugin-react(no-array-index-key)
|
||||
<RandomBar key={index} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import {
|
||||
DocsService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDialogService } from '../dialogs';
|
||||
import { DocsService } from '../doc';
|
||||
import { DocDisplayMetaService } from '../doc-display-meta';
|
||||
import { DocsSearchService } from '../docs-search';
|
||||
import { EditorSettingService } from '../editor-setting';
|
||||
import { JournalService } from '../journal';
|
||||
import { RecentDocsService } from '../quicksearch';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { AtMenuConfigService } from './services';
|
||||
|
||||
export function configAtMenuConfigModule(framework: Framework) {
|
||||
|
||||
@@ -18,18 +18,19 @@ import {
|
||||
} from '@blocksuite/icons/lit';
|
||||
import type { DocMeta } from '@blocksuite/store';
|
||||
import { signal } from '@preact/signals-core';
|
||||
import type { DocsService, WorkspaceService } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { html } from 'lit';
|
||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
|
||||
import type { WorkspaceDialogService } from '../../dialogs';
|
||||
import type { DocsService } from '../../doc';
|
||||
import type { DocDisplayMetaService } from '../../doc-display-meta';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
import type { EditorSettingService } from '../../editor-setting';
|
||||
import { type JournalService, suggestJournalDate } from '../../journal';
|
||||
import type { RecentDocsService } from '../../quicksearch';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
const MAX_DOCS = 3;
|
||||
const LOAD_CHUNK = 100;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { GetWorkspacePageMetaByIdQuery } from '@affine/graphql';
|
||||
import type { DocService, GlobalCache } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, mergeMap } from 'rxjs';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { GlobalCache } from '../../storage';
|
||||
import { isBackendError, isNetworkError } from '../error';
|
||||
import type { CloudDocMetaStore } from '../stores/cloud-doc-meta';
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { InvoicesQuery } from '@affine/graphql';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, map, mergeMap } from 'rxjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { isBackendError, isNetworkError } from '../error';
|
||||
import type { WorkspaceServerService } from '../services/workspace-server';
|
||||
import { InvoicesStore } from '../stores/invoices';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { SubscriptionQuery, SubscriptionRecurring } from '@affine/graphql';
|
||||
import { SubscriptionPlan } from '@affine/graphql';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
@@ -14,10 +13,10 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, mergeMap } from 'rxjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { isBackendError, isNetworkError } from '../error';
|
||||
import type { WorkspaceServerService } from '../services/workspace-server';
|
||||
import { SubscriptionStore } from '../stores/subscription';
|
||||
|
||||
export type SubscriptionType = NonNullable<
|
||||
SubscriptionQuery['currentUser']
|
||||
>['subscriptions'][number];
|
||||
|
||||
@@ -33,18 +33,12 @@ export { WorkspaceServerService } from './services/workspace-server';
|
||||
export { WorkspaceSubscriptionService } from './services/workspace-subscription';
|
||||
export type { ServerConfig } from './types';
|
||||
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
type Framework,
|
||||
GlobalCache,
|
||||
GlobalState,
|
||||
GlobalStateService,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocScope, DocService } from '../doc';
|
||||
import { GlobalCache, GlobalState, GlobalStateService } from '../storage';
|
||||
import { UrlService } from '../url';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { CloudDocMeta } from './entities/cloud-doc-meta';
|
||||
import { Invoices } from './entities/invoices';
|
||||
import { Server } from './entities/server';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||
import type { OAuthProviderType } from '@affine/graphql';
|
||||
import { track } from '@affine/track';
|
||||
import { ApplicationFocused, OnEvent, Service } from '@toeverything/infra';
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
import { distinctUntilChanged, map, skip } from 'rxjs';
|
||||
|
||||
import { ApplicationFocused } from '../../lifecycle';
|
||||
import type { UrlService } from '../../url';
|
||||
import { type AuthAccountInfo, AuthSession } from '../entities/session';
|
||||
import { BackendError } from '../error';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
import { Manager } from 'socket.io-client';
|
||||
|
||||
import { ApplicationStarted } from '../../lifecycle';
|
||||
import { AccountChanged } from '../events/account-changed';
|
||||
import type { WebSocketAuthProvider } from '../provider/websocket-auth';
|
||||
import type { AuthService } from './auth';
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
updateUserProfileMutation,
|
||||
uploadAvatarMutation,
|
||||
} from '@affine/graphql';
|
||||
import type { GlobalState } from '@toeverything/infra';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GlobalState } from '../../storage';
|
||||
import type { AuthSessionInfo } from '../entities/session';
|
||||
import type { FetchService } from '../services/fetch';
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GlobalStateService } from '@toeverything/infra';
|
||||
import { Store } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
import { BUILD_IN_SERVERS } from '../constant';
|
||||
import type { ServerConfig, ServerMetadata } from '../types';
|
||||
|
||||
|
||||
@@ -12,14 +12,13 @@ import {
|
||||
subscriptionQuery,
|
||||
updateSubscriptionMutation,
|
||||
} from '@affine/graphql';
|
||||
import type { GlobalCache } from '@toeverything/infra';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GlobalCache } from '../../storage';
|
||||
import type { UrlService } from '../../url';
|
||||
import type { SubscriptionType } from '../entities/subscription';
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
import type { ServerService } from '../services/server';
|
||||
|
||||
const SUBSCRIPTION_CACHE_KEY = 'subscription:';
|
||||
|
||||
const getDefaultSubscriptionSuccessCallbackLink = (
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
export { CollectionService } from './services/collection';
|
||||
|
||||
import {
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { CollectionService } from './services/collection';
|
||||
|
||||
export function configureCollectionModule(framework: Framework) {
|
||||
|
||||
@@ -3,11 +3,12 @@ import type {
|
||||
DeleteCollectionInfo,
|
||||
DeletedCollection,
|
||||
} from '@affine/env/filter';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Array as YArray } from 'yjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
const SETTING_KEY = 'setting';
|
||||
|
||||
const COLLECTIONS_KEY = 'collections';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
type DBSchemaBuilder,
|
||||
Entity,
|
||||
type TableMap,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDBTable } from './table';
|
||||
|
||||
export class WorkspaceDB<Schema extends DBSchemaBuilder> extends Entity<{
|
||||
db: TableMap<Schema>;
|
||||
schema: Schema;
|
||||
storageDocId: (tableName: string) => string;
|
||||
}> {
|
||||
readonly db = this.props.db;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
Object.entries(this.props.schema).forEach(([tableName]) => {
|
||||
const table = this.framework.createEntity(WorkspaceDBTable, {
|
||||
table: this.db[tableName],
|
||||
storageDocId: this.props.storageDocId(tableName),
|
||||
});
|
||||
Object.defineProperty(this, tableName, {
|
||||
get: () => table,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkspaceDBWithTables<Schema extends DBSchemaBuilder> =
|
||||
WorkspaceDB<Schema> & {
|
||||
[K in keyof Schema]: WorkspaceDBTable<Schema[K]>;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import type {
|
||||
Table as OrmTable,
|
||||
TableSchemaBuilder,
|
||||
} from '@toeverything/infra';
|
||||
import { Entity } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
export class WorkspaceDBTable<
|
||||
Schema extends TableSchemaBuilder,
|
||||
> extends Entity<{
|
||||
table: OrmTable<Schema>;
|
||||
storageDocId: string;
|
||||
}> {
|
||||
readonly table = this.props.table;
|
||||
|
||||
constructor(private readonly workspaceService: WorkspaceService) {
|
||||
super();
|
||||
}
|
||||
|
||||
isSyncing$ = this.workspaceService.workspace.engine.doc
|
||||
.docState$(this.props.storageDocId)
|
||||
.map(docState => docState.syncing);
|
||||
|
||||
isLoading$ = this.workspaceService.workspace.engine.doc
|
||||
.docState$(this.props.storageDocId)
|
||||
.map(docState => docState.loading);
|
||||
|
||||
create = this.table.create.bind(this.table) as typeof this.table.create;
|
||||
update = this.table.update.bind(this.table) as typeof this.table.update;
|
||||
get = this.table.get.bind(this.table) as typeof this.table.get;
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
get$ = this.table.get$.bind(this.table) as typeof this.table.get$;
|
||||
find = this.table.find.bind(this.table) as typeof this.table.find;
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
find$ = this.table.find$.bind(this.table) as typeof this.table.find$;
|
||||
keys = this.table.keys.bind(this.table) as typeof this.table.keys;
|
||||
delete = this.table.delete.bind(this.table) as typeof this.table.delete;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { WorkspaceDB } from './entities/db';
|
||||
import { WorkspaceDBTable } from './entities/table';
|
||||
import { WorkspaceDBService } from './services/db';
|
||||
|
||||
export type { DocCustomPropertyInfo, DocProperties } from './schema';
|
||||
export { WorkspaceDBService } from './services/db';
|
||||
export { transformWorkspaceDBLocalToCloud } from './services/db';
|
||||
|
||||
export function configureWorkspaceDBModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(WorkspaceDBService, [WorkspaceService])
|
||||
.entity(WorkspaceDB)
|
||||
.entity(WorkspaceDBTable, [WorkspaceService]);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type { DocCustomPropertyInfo, DocProperties } from './schema';
|
||||
export {
|
||||
AFFiNE_WORKSPACE_DB_SCHEMA,
|
||||
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
|
||||
type AFFiNEWorkspaceDbSchema,
|
||||
type AFFiNEWorkspaceUserdataDbSchema,
|
||||
} from './schema';
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
type DBSchemaBuilder,
|
||||
f,
|
||||
type ORMEntity,
|
||||
t,
|
||||
} from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export const AFFiNE_WORKSPACE_DB_SCHEMA = {
|
||||
folders: {
|
||||
id: f.string().primaryKey().optional().default(nanoid),
|
||||
parentId: f.string().optional(),
|
||||
data: f.string(),
|
||||
type: f.string(),
|
||||
index: f.string(),
|
||||
},
|
||||
docProperties: t.document({
|
||||
// { [`custom:{customPropertyId}`]: any }
|
||||
id: f.string().primaryKey(),
|
||||
primaryMode: f.string().optional(),
|
||||
edgelessColorTheme: f.string().optional(),
|
||||
journal: f.string().optional(),
|
||||
pageWidth: f.string().optional(),
|
||||
}),
|
||||
docCustomPropertyInfo: {
|
||||
id: f.string().primaryKey().optional().default(nanoid),
|
||||
name: f.string().optional(),
|
||||
type: f.string(),
|
||||
show: f.string().optional(),
|
||||
index: f.string().optional(),
|
||||
icon: f.string().optional(),
|
||||
additionalData: f.json().optional(),
|
||||
isDeleted: f.boolean().optional(),
|
||||
// we will keep deleted properties in the database, for override legacy data
|
||||
},
|
||||
} as const satisfies DBSchemaBuilder;
|
||||
export type AFFiNEWorkspaceDbSchema = typeof AFFiNE_WORKSPACE_DB_SCHEMA;
|
||||
|
||||
export type DocProperties = ORMEntity<AFFiNEWorkspaceDbSchema['docProperties']>;
|
||||
|
||||
export type DocCustomPropertyInfo = ORMEntity<
|
||||
AFFiNEWorkspaceDbSchema['docCustomPropertyInfo']
|
||||
>;
|
||||
|
||||
export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = {
|
||||
favorite: {
|
||||
key: f.string().primaryKey(),
|
||||
index: f.string(),
|
||||
},
|
||||
} as const satisfies DBSchemaBuilder;
|
||||
export type AFFiNEWorkspaceUserdataDbSchema =
|
||||
typeof AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA;
|
||||
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
createORMClient,
|
||||
type DocStorage,
|
||||
ObjectPool,
|
||||
Service,
|
||||
YjsDBAdapter,
|
||||
} from '@toeverything/infra';
|
||||
import { Doc as YDoc } from 'yjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db';
|
||||
import {
|
||||
AFFiNE_WORKSPACE_DB_SCHEMA,
|
||||
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
|
||||
type AFFiNEWorkspaceDbSchema,
|
||||
type AFFiNEWorkspaceUserdataDbSchema,
|
||||
} from '../schema';
|
||||
|
||||
const WorkspaceDBClient = createORMClient(AFFiNE_WORKSPACE_DB_SCHEMA);
|
||||
const WorkspaceUserdataDBClient = createORMClient(
|
||||
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA
|
||||
);
|
||||
|
||||
export class WorkspaceDBService extends Service {
|
||||
db: WorkspaceDBWithTables<AFFiNEWorkspaceDbSchema>;
|
||||
userdataDBPool = new ObjectPool<
|
||||
string,
|
||||
WorkspaceDB<AFFiNEWorkspaceUserdataDbSchema>
|
||||
>({
|
||||
onDangling() {
|
||||
return false; // never release
|
||||
},
|
||||
});
|
||||
|
||||
constructor(private readonly workspaceService: WorkspaceService) {
|
||||
super();
|
||||
this.db = this.framework.createEntity(
|
||||
WorkspaceDB<AFFiNEWorkspaceDbSchema>,
|
||||
{
|
||||
db: new WorkspaceDBClient(
|
||||
new YjsDBAdapter(AFFiNE_WORKSPACE_DB_SCHEMA, {
|
||||
getDoc: guid => {
|
||||
const ydoc = new YDoc({
|
||||
// guid format: db${workspaceId}${guid}
|
||||
guid: `db$${this.workspaceService.workspace.id}$${guid}`,
|
||||
});
|
||||
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
||||
this.workspaceService.workspace.engine.doc.setPriority(
|
||||
ydoc.guid,
|
||||
50
|
||||
);
|
||||
return ydoc;
|
||||
},
|
||||
})
|
||||
),
|
||||
schema: AFFiNE_WORKSPACE_DB_SCHEMA,
|
||||
storageDocId: tableName =>
|
||||
`db$${this.workspaceService.workspace.id}$${tableName}`,
|
||||
}
|
||||
) as WorkspaceDBWithTables<AFFiNEWorkspaceDbSchema>;
|
||||
}
|
||||
|
||||
userdataDB(userId: (string & {}) | '__local__') {
|
||||
// __local__ for local workspace
|
||||
const userdataDb = this.userdataDBPool.get(userId);
|
||||
if (userdataDb) {
|
||||
return userdataDb.obj as WorkspaceDBWithTables<AFFiNEWorkspaceUserdataDbSchema>;
|
||||
}
|
||||
|
||||
const newDB = this.framework.createEntity(
|
||||
WorkspaceDB<AFFiNEWorkspaceUserdataDbSchema>,
|
||||
{
|
||||
db: new WorkspaceUserdataDBClient(
|
||||
new YjsDBAdapter(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA, {
|
||||
getDoc: guid => {
|
||||
const ydoc = new YDoc({
|
||||
// guid format: userdata${userId}${workspaceId}${guid}
|
||||
guid: `userdata$${userId}$${this.workspaceService.workspace.id}$${guid}`,
|
||||
});
|
||||
this.workspaceService.workspace.engine.doc.addDoc(ydoc, false);
|
||||
this.workspaceService.workspace.engine.doc.setPriority(
|
||||
ydoc.guid,
|
||||
50
|
||||
);
|
||||
return ydoc;
|
||||
},
|
||||
})
|
||||
),
|
||||
schema: AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
|
||||
storageDocId: tableName =>
|
||||
`userdata$${userId}$${this.workspaceService.workspace.id}$${tableName}`,
|
||||
}
|
||||
);
|
||||
|
||||
this.userdataDBPool.put(userId, newDB);
|
||||
return newDB as WorkspaceDBWithTables<AFFiNEWorkspaceUserdataDbSchema>;
|
||||
}
|
||||
|
||||
static isDBDocId(docId: string) {
|
||||
return docId.startsWith('db$') || docId.startsWith('userdata$');
|
||||
}
|
||||
}
|
||||
|
||||
export async function transformWorkspaceDBLocalToCloud(
|
||||
localWorkspaceId: string,
|
||||
cloudWorkspaceId: string,
|
||||
localDocStorage: DocStorage,
|
||||
cloudDocStorage: DocStorage,
|
||||
accountId: string
|
||||
) {
|
||||
for (const tableName of Object.keys(AFFiNE_WORKSPACE_DB_SCHEMA)) {
|
||||
const localDocName = `db$${localWorkspaceId}$${tableName}`;
|
||||
const localDoc = await localDocStorage.doc.get(localDocName);
|
||||
if (localDoc) {
|
||||
const cloudDocName = `db$${cloudWorkspaceId}$${tableName}`;
|
||||
await cloudDocStorage.doc.set(cloudDocName, localDoc);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tableName of Object.keys(AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA)) {
|
||||
const localDocName = `userdata$__local__$${localWorkspaceId}$${tableName}`;
|
||||
const localDoc = await localDocStorage.doc.get(localDocName);
|
||||
if (localDoc) {
|
||||
const cloudDocName = `userdata$${accountId}$${cloudWorkspaceId}$${tableName}`;
|
||||
await cloudDocStorage.doc.set(cloudDocName, localDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
reactRouterV6BrowserTracingIntegration,
|
||||
setTags,
|
||||
} from '@sentry/react';
|
||||
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { AuthService, DefaultServerService, ServersService } from '../../cloud';
|
||||
import { ApplicationStarted } from '../../lifecycle';
|
||||
import type { DesktopApi } from '../entities/electron-api';
|
||||
|
||||
@OnEvent(ApplicationStarted, e => e.setupStartListener)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceMetadata } from '../workspace';
|
||||
|
||||
export type SettingTab =
|
||||
| 'shortcuts'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
import { WorkspaceScope } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { GlobalDialogService } from './services/dialog';
|
||||
import { WorkspaceDialogService } from './services/workspace-dialog';
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
DocsService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../doc';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { DndService } from './services';
|
||||
|
||||
export function configureDndModule(framework: Framework) {
|
||||
|
||||
@@ -8,10 +8,11 @@ import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { BlockStdScope } from '@blocksuite/affine/block-std';
|
||||
import { DndApiExtensionIdentifier } from '@blocksuite/affine/blocks';
|
||||
import { type SliceSnapshot } from '@blocksuite/affine/store';
|
||||
import type { DocsService, WorkspaceService } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import type { DocsService } from '../../doc';
|
||||
import { resolveLinkToDoc } from '../../navigation';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
type Entity = AffineDNDData['draggable']['entity'];
|
||||
type EntityResolver = (data: string) => Entity | null;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../doc';
|
||||
import { FeatureFlagService } from '../feature-flag';
|
||||
import { I18nService } from '../i18n';
|
||||
import { JournalService } from '../journal';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { DocDisplayMetaService } from './services/doc-display-meta';
|
||||
|
||||
export { DocDisplayMetaService };
|
||||
|
||||
@@ -22,15 +22,12 @@ import {
|
||||
TomorrowIcon,
|
||||
YesterdayIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import type {
|
||||
DocRecord,
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
} from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import type { DocRecord, DocsService } from '../../doc';
|
||||
import type { FeatureFlagService } from '../../feature-flag';
|
||||
import type { I18nService } from '../../i18n';
|
||||
import type { JournalService } from '../../journal';
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {
|
||||
DocsService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../doc/services/docs';
|
||||
import { DocsSearchService } from '../docs-search';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { DocDatabaseBacklinksService } from './services/doc-database-backlinks';
|
||||
|
||||
export { DocDatabaseBacklinkInfo } from './views/database-properties/doc-database-backlink-info';
|
||||
|
||||
@@ -2,11 +2,11 @@ import {
|
||||
DatabaseBlockDataSource,
|
||||
type DatabaseBlockModel,
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import type { DocsService } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { combineLatest, distinctUntilChanged, map, Observable } from 'rxjs';
|
||||
|
||||
import type { DocsService } from '../../doc';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
import type { DatabaseRow, DatabaseValueCell } from '../types';
|
||||
import { signalToLiveData, signalToObservable } from '../utils';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { DatabaseBlockDataSource } from '@blocksuite/affine/blocks';
|
||||
import type { Doc, LiveData } from '@toeverything/infra';
|
||||
import type { LiveData } from '@toeverything/infra';
|
||||
|
||||
import type { Doc } from '../doc';
|
||||
|
||||
// make database property type to be compatible with DocCustomPropertyInfo
|
||||
export type DatabaseProperty<Data = Record<string, unknown>> = {
|
||||
|
||||
+2
-6
@@ -5,15 +5,11 @@ import {
|
||||
PropertyName,
|
||||
} from '@affine/component';
|
||||
import { AffinePageReference } from '@affine/core/components/affine/reference-link';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { DatabaseBlockDataSource } from '@blocksuite/affine/blocks';
|
||||
import { DatabaseTableViewIcon, PageIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DocService } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
|
||||
export interface Backlink {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DocService } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { DocsSearchService } from '../../docs-search';
|
||||
|
||||
export interface Link {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocScope } from '../doc/scopes/doc';
|
||||
import { DocService } from '../doc/services/doc';
|
||||
import { DocsSearchService } from '../docs-search';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { DocBacklinks } from './entities/doc-backlinks';
|
||||
import { DocLinks } from './entities/doc-links';
|
||||
import { DocLinksService } from './services/doc-links';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { DocCustomPropertyInfo } from '../db';
|
||||
|
||||
/**
|
||||
* default built-in custom property, user can update and delete them
|
||||
*
|
||||
* 'id' and 'type' is request, 'index' is a manually maintained incremental key.
|
||||
*/
|
||||
export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
|
||||
{
|
||||
id: 'tags',
|
||||
type: 'tags',
|
||||
index: 'a0000001',
|
||||
},
|
||||
{
|
||||
id: 'docPrimaryMode',
|
||||
type: 'docPrimaryMode',
|
||||
show: 'always-hide',
|
||||
index: 'a0000002',
|
||||
},
|
||||
{
|
||||
id: 'journal',
|
||||
type: 'journal',
|
||||
show: 'always-hide',
|
||||
index: 'a0000003',
|
||||
},
|
||||
{
|
||||
id: 'createdAt',
|
||||
type: 'createdAt',
|
||||
index: 'a0000004',
|
||||
},
|
||||
{
|
||||
id: 'updatedAt',
|
||||
type: 'updatedAt',
|
||||
index: 'a0000005',
|
||||
},
|
||||
{
|
||||
id: 'createdBy',
|
||||
type: 'createdBy',
|
||||
show: 'always-hide',
|
||||
index: 'a0000006',
|
||||
},
|
||||
{
|
||||
id: 'edgelessTheme',
|
||||
type: 'edgelessTheme',
|
||||
show: 'always-hide',
|
||||
index: 'a0000007',
|
||||
},
|
||||
{
|
||||
id: 'pageWidth',
|
||||
type: 'pageWidth',
|
||||
show: 'always-hide',
|
||||
index: 'a0000008',
|
||||
},
|
||||
] as DocCustomPropertyInfo[];
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { DocMode, RootBlockModel } from '@blocksuite/affine/blocks';
|
||||
import { Entity } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { DocScope } from '../scopes/doc';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
|
||||
export class Doc extends Entity {
|
||||
constructor(
|
||||
public readonly scope: DocScope,
|
||||
private readonly store: DocsStore,
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* for convenience
|
||||
*/
|
||||
get workspace() {
|
||||
return this.workspaceService.workspace;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.scope.props.docId;
|
||||
}
|
||||
|
||||
public readonly blockSuiteDoc = this.scope.props.blockSuiteDoc;
|
||||
public readonly record = this.scope.props.record;
|
||||
|
||||
readonly meta$ = this.record.meta$;
|
||||
readonly properties$ = this.record.properties$;
|
||||
readonly primaryMode$ = this.record.primaryMode$;
|
||||
readonly title$ = this.record.title$;
|
||||
readonly trash$ = this.record.trash$;
|
||||
|
||||
customProperty$(propertyId: string) {
|
||||
return this.record.customProperty$(propertyId);
|
||||
}
|
||||
|
||||
setCustomProperty(propertyId: string, value: string) {
|
||||
return this.record.setCustomProperty(propertyId, value);
|
||||
}
|
||||
|
||||
setPrimaryMode(mode: DocMode) {
|
||||
return this.record.setPrimaryMode(mode);
|
||||
}
|
||||
|
||||
getPrimaryMode() {
|
||||
return this.record.getPrimaryMode();
|
||||
}
|
||||
|
||||
togglePrimaryMode() {
|
||||
this.setPrimaryMode(
|
||||
(this.getPrimaryMode() === 'edgeless' ? 'page' : 'edgeless') as DocMode
|
||||
);
|
||||
}
|
||||
|
||||
moveToTrash() {
|
||||
return this.record.moveToTrash();
|
||||
}
|
||||
|
||||
restoreFromTrash() {
|
||||
return this.record.restoreFromTrash();
|
||||
}
|
||||
|
||||
waitForSyncReady() {
|
||||
return this.store.waitForDocLoadReady(this.id);
|
||||
}
|
||||
|
||||
setPriorityLoad(priority: number) {
|
||||
return this.store.setPriorityLoad(this.id, priority);
|
||||
}
|
||||
|
||||
changeDocTitle(newTitle: string) {
|
||||
const pageBlock = this.blockSuiteDoc.getBlocksByFlavour('affine:page').at(0)
|
||||
?.model as RootBlockModel | undefined;
|
||||
if (pageBlock) {
|
||||
this.blockSuiteDoc.transact(() => {
|
||||
pageBlock.title.delete(0, pageBlock.title.length);
|
||||
pageBlock.title.insert(newTitle, 0);
|
||||
});
|
||||
this.record.setMeta({ title: newTitle });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
Entity,
|
||||
generateFractionalIndexingKeyBetween,
|
||||
LiveData,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import type { DocCustomPropertyInfo } from '../../db/schema/schema';
|
||||
import type { DocPropertiesStore } from '../stores/doc-properties';
|
||||
|
||||
export class DocPropertyList extends Entity {
|
||||
constructor(private readonly docPropertiesStore: DocPropertiesStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
properties$ = LiveData.from(
|
||||
this.docPropertiesStore.watchDocPropertyInfoList(),
|
||||
[]
|
||||
);
|
||||
|
||||
sortedProperties$ = this.properties$.map(list =>
|
||||
// default index key is '', so always before any others
|
||||
list.toSorted((a, b) => ((a.index ?? '') > (b.index ?? '') ? 1 : -1))
|
||||
);
|
||||
|
||||
propertyInfo$(id: string) {
|
||||
return this.properties$.map(list => list.find(info => info.id === id));
|
||||
}
|
||||
|
||||
updatePropertyInfo(id: string, properties: Partial<DocCustomPropertyInfo>) {
|
||||
this.docPropertiesStore.updateDocPropertyInfo(id, properties);
|
||||
}
|
||||
|
||||
createProperty(
|
||||
properties: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.docPropertiesStore.createDocPropertyInfo(properties);
|
||||
}
|
||||
|
||||
removeProperty(id: string) {
|
||||
this.docPropertiesStore.removeDocPropertyInfo(id);
|
||||
}
|
||||
|
||||
indexAt(at: 'before' | 'after', targetId?: string) {
|
||||
const sortedChildren = this.sortedProperties$.value.filter(
|
||||
node => node.index
|
||||
) as (DocCustomPropertyInfo & { index: string })[];
|
||||
const targetIndex = targetId
|
||||
? sortedChildren.findIndex(node => node.id === targetId)
|
||||
: -1;
|
||||
if (targetIndex === -1) {
|
||||
if (at === 'before') {
|
||||
const first = sortedChildren.at(0);
|
||||
return generateFractionalIndexingKeyBetween(null, first?.index ?? null);
|
||||
} else {
|
||||
const last = sortedChildren.at(-1);
|
||||
return generateFractionalIndexingKeyBetween(last?.index ?? null, null);
|
||||
}
|
||||
} else {
|
||||
const target = sortedChildren[targetIndex];
|
||||
const before: DocCustomPropertyInfo | null =
|
||||
sortedChildren[targetIndex - 1] || null;
|
||||
const after: DocCustomPropertyInfo | null =
|
||||
sortedChildren[targetIndex + 1] || null;
|
||||
if (at === 'before') {
|
||||
return generateFractionalIndexingKeyBetween(
|
||||
before?.index ?? null,
|
||||
target.index
|
||||
);
|
||||
} else {
|
||||
return generateFractionalIndexingKeyBetween(
|
||||
target.index,
|
||||
after?.index ?? null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
import { DocRecord } from './record';
|
||||
|
||||
export class DocRecordList extends Entity {
|
||||
constructor(private readonly store: DocsStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
private readonly pool = new Map<string, DocRecord>();
|
||||
|
||||
public readonly docsMap$ = LiveData.from<Map<string, DocRecord>>(
|
||||
this.store.watchDocIds().pipe(
|
||||
map(
|
||||
ids =>
|
||||
new Map(
|
||||
ids.map(id => {
|
||||
const exists = this.pool.get(id);
|
||||
if (exists) {
|
||||
return [id, exists];
|
||||
}
|
||||
const record = this.framework.createEntity(DocRecord, { id });
|
||||
this.pool.set(id, record);
|
||||
return [id, record];
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
new Map()
|
||||
);
|
||||
|
||||
public readonly docs$ = this.docsMap$.selector(d => Array.from(d.values()));
|
||||
|
||||
public readonly trashDocs$ = LiveData.from<DocRecord[]>(
|
||||
this.store.watchTrashDocIds().pipe(
|
||||
map(ids =>
|
||||
ids.map(id => {
|
||||
const exists = this.pool.get(id);
|
||||
if (exists) {
|
||||
return exists;
|
||||
}
|
||||
const record = this.framework.createEntity(DocRecord, { id });
|
||||
this.pool.set(id, record);
|
||||
return record;
|
||||
})
|
||||
)
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
public readonly isReady$ = LiveData.from(
|
||||
this.store.watchDocListReady(),
|
||||
false
|
||||
);
|
||||
|
||||
public doc$(id: string) {
|
||||
return this.docsMap$.selector(map => map.get(id));
|
||||
}
|
||||
|
||||
public setPrimaryMode(id: string, mode: DocMode) {
|
||||
return this.store.setDocPrimaryModeSetting(id, mode);
|
||||
}
|
||||
|
||||
public getPrimaryMode(id: string) {
|
||||
return this.store.getDocPrimaryModeSetting(id);
|
||||
}
|
||||
|
||||
public togglePrimaryMode(id: string) {
|
||||
const mode = (
|
||||
this.getPrimaryMode(id) === 'edgeless' ? 'page' : 'edgeless'
|
||||
) as DocMode;
|
||||
this.setPrimaryMode(id, mode);
|
||||
return this.getPrimaryMode(id);
|
||||
}
|
||||
|
||||
public primaryMode$(id: string) {
|
||||
return LiveData.from(
|
||||
this.store.watchDocPrimaryModeSetting(id),
|
||||
this.getPrimaryMode(id)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import type { DocProperties } from '../../db';
|
||||
import type { DocPropertiesStore } from '../stores/doc-properties';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
|
||||
/**
|
||||
* # DocRecord
|
||||
*
|
||||
* Some data you can use without open a doc.
|
||||
*/
|
||||
export class DocRecord extends Entity<{ id: string }> {
|
||||
id: string = this.props.id;
|
||||
constructor(
|
||||
private readonly docsStore: DocsStore,
|
||||
private readonly docPropertiesStore: DocPropertiesStore
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
meta$ = LiveData.from<Partial<DocMeta>>(
|
||||
this.docsStore.watchDocMeta(this.id),
|
||||
{}
|
||||
);
|
||||
|
||||
properties$ = LiveData.from<DocProperties>(
|
||||
this.docPropertiesStore.watchDocProperties(this.id),
|
||||
{ id: this.id }
|
||||
);
|
||||
|
||||
customProperty$(propertyId: string) {
|
||||
return this.properties$.selector(
|
||||
p => p['custom:' + propertyId]
|
||||
) as LiveData<string | undefined | null>;
|
||||
}
|
||||
|
||||
setCustomProperty(propertyId: string, value: string) {
|
||||
this.docPropertiesStore.updateDocProperties(this.id, {
|
||||
['custom:' + propertyId]: value,
|
||||
});
|
||||
}
|
||||
|
||||
setProperty(propertyId: string, value: string) {
|
||||
this.docPropertiesStore.updateDocProperties(this.id, {
|
||||
[propertyId]: value,
|
||||
});
|
||||
}
|
||||
|
||||
setMeta(meta: Partial<DocMeta>): void {
|
||||
this.docsStore.setDocMeta(this.id, meta);
|
||||
}
|
||||
|
||||
primaryMode$: LiveData<DocMode> = LiveData.from(
|
||||
this.docsStore.watchDocPrimaryModeSetting(this.id),
|
||||
'page' as DocMode
|
||||
).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page') as DocMode);
|
||||
|
||||
setPrimaryMode(mode: DocMode) {
|
||||
return this.docsStore.setDocPrimaryModeSetting(this.id, mode);
|
||||
}
|
||||
|
||||
getPrimaryMode() {
|
||||
return this.docsStore.getDocPrimaryModeSetting(this.id);
|
||||
}
|
||||
|
||||
moveToTrash() {
|
||||
return this.setMeta({ trash: true, trashDate: Date.now() });
|
||||
}
|
||||
|
||||
restoreFromTrash() {
|
||||
return this.setMeta({ trash: false, trashDate: undefined });
|
||||
}
|
||||
|
||||
title$ = this.meta$.map(meta => meta.title ?? '');
|
||||
|
||||
trash$ = this.meta$.map(meta => meta.trash ?? false);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createEvent } from '@toeverything/infra';
|
||||
|
||||
import type { DocRecord } from '../entities/record';
|
||||
|
||||
export const DocCreated = createEvent<DocRecord>('DocCreated');
|
||||
@@ -0,0 +1,35 @@
|
||||
export { Doc } from './entities/doc';
|
||||
export { DocRecord } from './entities/record';
|
||||
export { DocRecordList } from './entities/record-list';
|
||||
export { DocCreated } from './events';
|
||||
export { DocScope } from './scopes/doc';
|
||||
export { DocService } from './services/doc';
|
||||
export { DocsService } from './services/docs';
|
||||
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDBService } from '../db';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { Doc } from './entities/doc';
|
||||
import { DocPropertyList } from './entities/property-list';
|
||||
import { DocRecord } from './entities/record';
|
||||
import { DocRecordList } from './entities/record-list';
|
||||
import { DocScope } from './scopes/doc';
|
||||
import { DocService } from './services/doc';
|
||||
import { DocsService } from './services/docs';
|
||||
import { DocPropertiesStore } from './stores/doc-properties';
|
||||
import { DocsStore } from './stores/docs';
|
||||
|
||||
export function configureDocModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(DocsService, [DocsStore])
|
||||
.store(DocPropertiesStore, [WorkspaceService, WorkspaceDBService])
|
||||
.store(DocsStore, [WorkspaceService, DocPropertiesStore])
|
||||
.entity(DocRecord, [DocsStore, DocPropertiesStore])
|
||||
.entity(DocRecordList, [DocsStore])
|
||||
.entity(DocPropertyList, [DocPropertiesStore])
|
||||
.scope(DocScope)
|
||||
.entity(Doc, [DocScope, DocsStore, WorkspaceService])
|
||||
.service(DocService);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Doc as BlockSuiteDoc } from '@blocksuite/affine/store';
|
||||
import { Scope } from '@toeverything/infra';
|
||||
|
||||
import type { DocRecord } from '../entities/record';
|
||||
|
||||
export class DocScope extends Scope<{
|
||||
docId: string;
|
||||
record: DocRecord;
|
||||
blockSuiteDoc: BlockSuiteDoc;
|
||||
}> {}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { Doc } from '../entities/doc';
|
||||
|
||||
export class DocService extends Service {
|
||||
public readonly doc = this.framework.createEntity(Doc);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import type { DeltaInsert } from '@blocksuite/affine/inline';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
type DocProps,
|
||||
initDocFromProps,
|
||||
ObjectPool,
|
||||
Service,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import type { Doc } from '../entities/doc';
|
||||
import { DocPropertyList } from '../entities/property-list';
|
||||
import { DocRecordList } from '../entities/record-list';
|
||||
import { DocCreated } from '../events';
|
||||
import { DocScope } from '../scopes/doc';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
import { DocService } from './doc';
|
||||
|
||||
const logger = new DebugLogger('DocsService');
|
||||
|
||||
export class DocsService extends Service {
|
||||
list = this.framework.createEntity(DocRecordList);
|
||||
|
||||
pool = new ObjectPool<string, Doc>({
|
||||
onDelete(obj) {
|
||||
obj.scope.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
propertyList = this.framework.createEntity(DocPropertyList);
|
||||
|
||||
constructor(private readonly store: DocsStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
open(docId: string) {
|
||||
const docRecord = this.list.doc$(docId).value;
|
||||
if (!docRecord) {
|
||||
throw new Error('Doc record not found');
|
||||
}
|
||||
const blockSuiteDoc = this.store.getBlockSuiteDoc(docId);
|
||||
if (!blockSuiteDoc) {
|
||||
throw new Error('Doc not found');
|
||||
}
|
||||
|
||||
const exists = this.pool.get(docId);
|
||||
if (exists) {
|
||||
return { doc: exists.obj, release: exists.release };
|
||||
}
|
||||
|
||||
const docScope = this.framework.createScope(DocScope, {
|
||||
docId,
|
||||
blockSuiteDoc,
|
||||
record: docRecord,
|
||||
});
|
||||
|
||||
try {
|
||||
blockSuiteDoc.load();
|
||||
} catch (e) {
|
||||
logger.error('Failed to load doc', {
|
||||
docId,
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
|
||||
const doc = docScope.get(DocService).doc;
|
||||
|
||||
const { obj, release } = this.pool.put(docId, doc);
|
||||
|
||||
return { doc: obj, release };
|
||||
}
|
||||
|
||||
createDoc(
|
||||
options: {
|
||||
primaryMode?: DocMode;
|
||||
docProps?: DocProps;
|
||||
} = {}
|
||||
) {
|
||||
const doc = this.store.createBlockSuiteDoc();
|
||||
initDocFromProps(doc, options.docProps);
|
||||
this.store.markDocSyncStateAsReady(doc.id);
|
||||
const docRecord = this.list.doc$(doc.id).value;
|
||||
if (!docRecord) {
|
||||
throw new Unreachable();
|
||||
}
|
||||
if (options.primaryMode) {
|
||||
docRecord.setPrimaryMode(options.primaryMode);
|
||||
}
|
||||
this.eventBus.emit(DocCreated, docRecord);
|
||||
return docRecord;
|
||||
}
|
||||
|
||||
async addLinkedDoc(targetDocId: string, linkedDocId: string) {
|
||||
const { doc, release } = this.open(targetDocId);
|
||||
doc.setPriorityLoad(10);
|
||||
await doc.waitForSyncReady();
|
||||
const text = new doc.blockSuiteDoc.Text([
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
reference: {
|
||||
type: 'LinkedPage',
|
||||
pageId: linkedDocId,
|
||||
},
|
||||
},
|
||||
},
|
||||
] as DeltaInsert<AffineTextAttributes>[]);
|
||||
const [frame] = doc.blockSuiteDoc.getBlocksByFlavour('affine:note');
|
||||
frame &&
|
||||
doc.blockSuiteDoc.addBlock(
|
||||
'affine:paragraph' as never, // TODO(eyhn): fix type
|
||||
{ text },
|
||||
frame.id
|
||||
);
|
||||
release();
|
||||
}
|
||||
|
||||
async changeDocTitle(docId: string, newTitle: string) {
|
||||
const { doc, release } = this.open(docId);
|
||||
doc.setPriorityLoad(10);
|
||||
await doc.waitForSyncReady();
|
||||
doc.changeDocTitle(newTitle);
|
||||
release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
import { Store, yjsObserveByPath, yjsObserveDeep } from '@toeverything/infra';
|
||||
import { differenceBy, isNil, omitBy } from 'lodash-es';
|
||||
import { combineLatest, map, switchMap } from 'rxjs';
|
||||
import { AbstractType as YAbstractType } from 'yjs';
|
||||
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
import type {
|
||||
DocCustomPropertyInfo,
|
||||
DocProperties,
|
||||
} from '../../db/schema/schema';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { BUILT_IN_CUSTOM_PROPERTY_TYPE } from '../constants';
|
||||
|
||||
interface LegacyDocProperties {
|
||||
custom?: Record<string, { value: unknown } | undefined>;
|
||||
system?: Record<string, { value: unknown } | undefined>;
|
||||
}
|
||||
|
||||
type LegacyDocPropertyInfo = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
type LegacyDocPropertyInfoList = Record<
|
||||
string,
|
||||
LegacyDocPropertyInfo | undefined
|
||||
>;
|
||||
|
||||
export class DocPropertiesStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly dbService: WorkspaceDBService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
updateDocProperties(id: string, config: Partial<DocProperties>) {
|
||||
return this.dbService.db.docProperties.create({
|
||||
id,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
getDocPropertyInfoList() {
|
||||
const db = this.dbService.db.docCustomPropertyInfo.find();
|
||||
const legacy = this.upgradeLegacyDocPropertyInfoList(
|
||||
this.getLegacyDocPropertyInfoList()
|
||||
);
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
}
|
||||
|
||||
createDocPropertyInfo(
|
||||
config: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.dbService.db.docCustomPropertyInfo.create(config);
|
||||
}
|
||||
|
||||
removeDocPropertyInfo(id: string) {
|
||||
this.updateDocPropertyInfo(id, {
|
||||
additionalData: {}, // also remove additional data to reduce size
|
||||
isDeleted: true,
|
||||
});
|
||||
}
|
||||
|
||||
updateDocPropertyInfo(id: string, config: Partial<DocCustomPropertyInfo>) {
|
||||
const needMigration = !this.dbService.db.docCustomPropertyInfo.get(id);
|
||||
const isBuiltIn =
|
||||
needMigration && BUILT_IN_CUSTOM_PROPERTY_TYPE.some(i => i.id === id);
|
||||
if (isBuiltIn) {
|
||||
this.createPropertyFromBuiltIn(id, config);
|
||||
} else if (needMigration) {
|
||||
// if this property is not in db, we need to migration it from legacy to db, only type and name is needed
|
||||
this.migrateLegacyDocPropertyInfo(id, config);
|
||||
} else {
|
||||
this.dbService.db.docCustomPropertyInfo.update(id, config);
|
||||
}
|
||||
}
|
||||
|
||||
migrateLegacyDocPropertyInfo(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const legacy = this.getLegacyDocPropertyInfo(id);
|
||||
this.dbService.db.docCustomPropertyInfo.create({
|
||||
id,
|
||||
type:
|
||||
legacy?.type ??
|
||||
'unknown' /* should never reach here, just for safety, we need handle unknown property type */,
|
||||
name: legacy?.name,
|
||||
...override,
|
||||
});
|
||||
}
|
||||
|
||||
createPropertyFromBuiltIn(
|
||||
id: string,
|
||||
override: Partial<DocCustomPropertyInfo>
|
||||
) {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE.find(i => i.id === id);
|
||||
if (!builtIn) {
|
||||
return;
|
||||
}
|
||||
this.createDocPropertyInfo({ ...builtIn, ...override });
|
||||
}
|
||||
|
||||
watchDocPropertyInfoList() {
|
||||
return combineLatest([
|
||||
this.watchLegacyDocPropertyInfoList().pipe(
|
||||
map(this.upgradeLegacyDocPropertyInfoList)
|
||||
),
|
||||
this.dbService.db.docCustomPropertyInfo.find$(),
|
||||
]).pipe(
|
||||
map(([legacy, db]) => {
|
||||
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
|
||||
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
|
||||
const all = [
|
||||
...withLegacy,
|
||||
...differenceBy(builtIn, withLegacy, i => i.id),
|
||||
];
|
||||
return all.filter(i => !i.isDeleted);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getDocProperties(id: string) {
|
||||
return {
|
||||
...this.upgradeLegacyDocProperties(this.getLegacyDocProperties(id)),
|
||||
...omitBy(this.dbService.db.docProperties.get(id), isNil),
|
||||
// db always override legacy, but nil value should not override
|
||||
};
|
||||
}
|
||||
|
||||
watchDocProperties(id: string) {
|
||||
return combineLatest([
|
||||
this.watchLegacyDocProperties(id).pipe(
|
||||
map(this.upgradeLegacyDocProperties)
|
||||
),
|
||||
this.dbService.db.docProperties.get$(id),
|
||||
]).pipe(
|
||||
map(
|
||||
([legacy, db]) =>
|
||||
({
|
||||
...legacy,
|
||||
...omitBy(db, isNil), // db always override legacy, but nil value should not override
|
||||
}) as DocProperties
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private upgradeLegacyDocProperties(properties?: LegacyDocProperties) {
|
||||
if (!properties) {
|
||||
return {};
|
||||
}
|
||||
const newProperties: Record<string, string> = {};
|
||||
for (const [key, info] of Object.entries(properties.system ?? {})) {
|
||||
if (info?.value !== undefined && info.value !== null) {
|
||||
newProperties[key] = info.value.toString();
|
||||
}
|
||||
}
|
||||
for (const [key, info] of Object.entries(properties.custom ?? {})) {
|
||||
if (info?.value !== undefined && info.value !== null) {
|
||||
newProperties['custom:' + key] = info.value.toString();
|
||||
}
|
||||
}
|
||||
return newProperties;
|
||||
}
|
||||
|
||||
private upgradeLegacyDocPropertyInfoList(
|
||||
infoList?: LegacyDocPropertyInfoList
|
||||
) {
|
||||
if (!infoList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newInfoList: DocCustomPropertyInfo[] = [];
|
||||
|
||||
for (const [id, info] of Object.entries(infoList ?? {})) {
|
||||
if (info?.type) {
|
||||
newInfoList.push({
|
||||
id,
|
||||
name: info.name,
|
||||
type: info.type,
|
||||
icon: info.icon,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return newInfoList;
|
||||
}
|
||||
|
||||
private getLegacyDocProperties(id: string) {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('pageProperties')
|
||||
?.get(id)
|
||||
?.toJSON() as LegacyDocProperties | undefined;
|
||||
}
|
||||
|
||||
private watchLegacyDocProperties(id: string) {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
`pageProperties.${id}`
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(
|
||||
p =>
|
||||
(p instanceof YAbstractType ? p.toJSON() : p) as
|
||||
| LegacyDocProperties
|
||||
| undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getLegacyDocPropertyInfoList() {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.toJSON() as LegacyDocPropertyInfoList | undefined;
|
||||
}
|
||||
|
||||
private watchLegacyDocPropertyInfoList() {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap<any>(
|
||||
'affine:workspace-properties'
|
||||
),
|
||||
'schema.pageProperties.custom'
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(
|
||||
p =>
|
||||
(p instanceof YAbstractType ? p.toJSON() : p) as
|
||||
| LegacyDocPropertyInfoList
|
||||
| undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getLegacyDocPropertyInfo(id: string) {
|
||||
return this.workspaceService.workspace.rootYDoc
|
||||
.getMap<any>('affine:workspace-properties')
|
||||
.get('schema')
|
||||
?.get('pageProperties')
|
||||
?.get('custom')
|
||||
?.get(id)
|
||||
?.toJSON() as LegacyDocPropertyInfo | undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import {
|
||||
Store,
|
||||
yjsObserve,
|
||||
yjsObserveByPath,
|
||||
yjsObserveDeep,
|
||||
} from '@toeverything/infra';
|
||||
import { distinctUntilChanged, map, switchMap } from 'rxjs';
|
||||
import { Array as YArray, Map as YMap } from 'yjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { DocPropertiesStore } from './doc-properties';
|
||||
|
||||
export class DocsStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly docPropertiesStore: DocPropertiesStore
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getBlockSuiteDoc(id: string) {
|
||||
return this.workspaceService.workspace.docCollection.getDoc(id);
|
||||
}
|
||||
|
||||
createBlockSuiteDoc() {
|
||||
return this.workspaceService.workspace.docCollection.createDoc();
|
||||
}
|
||||
|
||||
watchDocIds() {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(yjsObserve),
|
||||
map(meta => {
|
||||
if (meta instanceof YArray) {
|
||||
return meta.map(v => v.get('id') as string);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchTrashDocIds() {
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(yjsObserveDeep),
|
||||
map(meta => {
|
||||
if (meta instanceof YArray) {
|
||||
return meta
|
||||
.map(v => (v.get('trash') ? v.get('id') : null))
|
||||
.filter(Boolean) as string[];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchDocMeta(id: string) {
|
||||
let docMetaIndexCache = -1;
|
||||
return yjsObserveByPath(
|
||||
this.workspaceService.workspace.rootYDoc.getMap('meta'),
|
||||
'pages'
|
||||
).pipe(
|
||||
switchMap(yjsObserve),
|
||||
map(meta => {
|
||||
if (meta instanceof YArray) {
|
||||
if (docMetaIndexCache >= 0) {
|
||||
const doc = meta.get(docMetaIndexCache);
|
||||
if (doc && doc.get('id') === id) {
|
||||
return doc as YMap<any>;
|
||||
}
|
||||
}
|
||||
|
||||
// meta is YArray, `for-of` is faster then `for`
|
||||
let i = 0;
|
||||
for (const doc of meta) {
|
||||
if (doc && doc.get('id') === id) {
|
||||
docMetaIndexCache = i;
|
||||
return doc as YMap<any>;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
switchMap(yjsObserveDeep),
|
||||
map(meta => {
|
||||
if (meta instanceof YMap) {
|
||||
return meta.toJSON() as Partial<DocMeta>;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watchDocListReady() {
|
||||
return this.workspaceService.workspace.engine.rootDocState$
|
||||
.map(state => !state.syncing)
|
||||
.asObservable();
|
||||
}
|
||||
|
||||
setDocMeta(id: string, meta: Partial<DocMeta>) {
|
||||
this.workspaceService.workspace.docCollection.setDocMeta(id, meta);
|
||||
}
|
||||
|
||||
setDocPrimaryModeSetting(id: string, mode: DocMode) {
|
||||
return this.docPropertiesStore.updateDocProperties(id, {
|
||||
primaryMode: mode,
|
||||
});
|
||||
}
|
||||
|
||||
getDocPrimaryModeSetting(id: string) {
|
||||
return this.docPropertiesStore.getDocProperties(id)?.primaryMode;
|
||||
}
|
||||
|
||||
watchDocPrimaryModeSetting(id: string) {
|
||||
return this.docPropertiesStore.watchDocProperties(id).pipe(
|
||||
map(config => config?.primaryMode),
|
||||
distinctUntilChanged((p, c) => p === c)
|
||||
);
|
||||
}
|
||||
|
||||
waitForDocLoadReady(id: string) {
|
||||
return this.workspaceService.workspace.engine.doc.waitForReady(id);
|
||||
}
|
||||
|
||||
setPriorityLoad(id: string, priority: number) {
|
||||
return this.workspaceService.workspace.engine.doc.setPriority(id, priority);
|
||||
}
|
||||
|
||||
markDocSyncStateAsReady(id: string) {
|
||||
this.workspaceService.workspace.engine.doc.markAsReady(id);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type {
|
||||
Job,
|
||||
JobQueue,
|
||||
WorkspaceLocalState,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import type { Job, JobQueue } from '@toeverything/infra';
|
||||
import {
|
||||
Entity,
|
||||
IndexedDBIndexStorage,
|
||||
IndexedDBJobQueue,
|
||||
JobRunner,
|
||||
LiveData,
|
||||
WorkspaceDBService,
|
||||
} from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { WorkspaceDBService } from '../../db';
|
||||
import type { WorkspaceLocalState, WorkspaceService } from '../../workspace';
|
||||
import { blockIndexSchema, docIndexSchema } from '../schema';
|
||||
import { createWorker, type IndexerWorker } from '../worker/out-worker';
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export { DocsSearchService } from './services/docs-search';
|
||||
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import {
|
||||
type Framework,
|
||||
WorkspaceLocalState,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
} from '../workspace';
|
||||
import { DocsIndexer } from './entities/docs-indexer';
|
||||
import { DocsSearchService } from './services/docs-search';
|
||||
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||
import type { ReferenceParams } from '@blocksuite/blocks';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
fromPromise,
|
||||
OnEvent,
|
||||
Service,
|
||||
WorkspaceEngineBeforeStart,
|
||||
} from '@toeverything/infra';
|
||||
import { fromPromise, OnEvent, Service } from '@toeverything/infra';
|
||||
import { isEmpty, omit } from 'lodash-es';
|
||||
import { map, type Observable, switchMap } from 'rxjs';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { WorkspaceEngineBeforeStart } from '../../workspace';
|
||||
import { DocsIndexer } from '../entities/docs-indexer';
|
||||
|
||||
@OnEvent(WorkspaceEngineBeforeStart, s => s.handleWorkspaceEngineBeforeStart)
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@blocksuite/affine/store';
|
||||
import type { AffineTextAttributes } from '@blocksuite/affine-shared/types';
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import { Document, getAFFiNEWorkspaceSchema } from '@toeverything/infra';
|
||||
import { Document } from '@toeverything/infra';
|
||||
import { toHexString } from 'lib0/buffer.js';
|
||||
import { digest as lib0Digest } from 'lib0/hash/sha256';
|
||||
import { difference, uniq } from 'lodash-es';
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Text as YText,
|
||||
} from 'yjs';
|
||||
|
||||
import { getAFFiNEWorkspaceSchema } from '../../workspace/global-schema';
|
||||
import type { BlockIndexSchema, DocIndexSchema } from '../schema';
|
||||
import type {
|
||||
WorkerIngoingMessage,
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Framework, GlobalState, MemoryMemento } from '@toeverything/infra';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import { EditorSetting } from '../entities/editor-setting';
|
||||
import { GlobalStateEditorSettingProvider } from '../impls/global-state';
|
||||
import { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||
import { EditorSettingService } from '../services/editor-setting';
|
||||
|
||||
test('editor setting service', () => {
|
||||
const framework = new Framework();
|
||||
|
||||
framework
|
||||
.service(EditorSettingService)
|
||||
.entity(EditorSetting, [EditorSettingProvider])
|
||||
.impl(EditorSettingProvider, GlobalStateEditorSettingProvider, [
|
||||
GlobalState,
|
||||
])
|
||||
.impl(GlobalState, MemoryMemento);
|
||||
|
||||
const provider = framework.provider();
|
||||
|
||||
const editorSettingService = provider.get(EditorSettingService);
|
||||
|
||||
// default value
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||
|
||||
// set plain object
|
||||
editorSettingService.editorSetting.set('fontFamily', 'Serif');
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Serif');
|
||||
|
||||
// set nested object
|
||||
editorSettingService.editorSetting.set('connector', {
|
||||
stroke: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
},
|
||||
});
|
||||
expect(editorSettingService.editorSetting.get('connector').stroke).toEqual({
|
||||
dark: '#000000',
|
||||
light: '#ffffff',
|
||||
});
|
||||
|
||||
// invalid font family
|
||||
editorSettingService.editorSetting.provider.set(
|
||||
'fontFamily',
|
||||
JSON.stringify('abc')
|
||||
);
|
||||
|
||||
// fallback to default value
|
||||
expect(editorSettingService.editorSetting.get('fontFamily')).toBe('Sans');
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GlobalState } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable } from 'rxjs';
|
||||
|
||||
import type { GlobalState } from '../../storage';
|
||||
import type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||
|
||||
const storageKey = 'editor-setting';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { GlobalState } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
import { map, type Observable, switchMap } from 'rxjs';
|
||||
|
||||
import type { ServersService } from '../../cloud';
|
||||
import type { GlobalState } from '../../storage';
|
||||
import { UserDBService } from '../../userspace';
|
||||
import type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import {
|
||||
type Framework,
|
||||
GlobalState,
|
||||
GlobalStateService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { ServersService } from '../cloud';
|
||||
import { DesktopApiService } from '../desktop-api';
|
||||
import { I18n } from '../i18n';
|
||||
import { GlobalState, GlobalStateService } from '../storage';
|
||||
import { EditorSetting } from './entities/editor-setting';
|
||||
import { CurrentUserDBEditorSettingProvider } from './impls/user-db';
|
||||
import { EditorSettingProvider } from './provider/editor-setting-provider';
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import {
|
||||
DocsService,
|
||||
OnEvent,
|
||||
Service,
|
||||
WorkspaceInitialized,
|
||||
} from '@toeverything/infra';
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../../doc';
|
||||
import type { Workspace } from '../../workspace';
|
||||
import { WorkspaceInitialized } from '../../workspace';
|
||||
import {
|
||||
EditorSetting,
|
||||
type EditorSettingExt,
|
||||
|
||||
@@ -3,11 +3,11 @@ import type {
|
||||
SpellCheckStateSchema,
|
||||
} from '@affine/electron/main/shared-state-schema';
|
||||
import type { Language } from '@affine/i18n';
|
||||
import type { GlobalStateService } from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
|
||||
import type { DesktopApiService } from '../../desktop-api';
|
||||
import type { I18n } from '../../i18n';
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
|
||||
const SPELL_CHECK_SETTING_KEY: typeof SpellCheckStateKey = 'spellCheckState';
|
||||
|
||||
|
||||
@@ -10,13 +10,14 @@ import type {
|
||||
} from '@blocksuite/affine/presets';
|
||||
import type { InlineEditor } from '@blocksuite/inline';
|
||||
import { effect } from '@preact/signals-core';
|
||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { defaults, isEqual, omit } from 'lodash-es';
|
||||
import { skip } from 'rxjs';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
|
||||
import type { WorkbenchView } from '../../workbench';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import { EditorScope } from '../scopes/editor';
|
||||
import type { EditorSelector } from '../types';
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocScope, DocService } from '../doc';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { Editor } from './entities/editor';
|
||||
import { EditorScope } from './scopes/editor';
|
||||
import { EditorService } from './services/editor';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GlobalCache } from '@toeverything/infra';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { GlobalCache } from '../../storage';
|
||||
import type { CollapsibleSectionName } from '../types';
|
||||
|
||||
const DEFAULT_COLLAPSABLE_STATE: Record<CollapsibleSectionName, boolean> = {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
type Framework,
|
||||
GlobalCache,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalCache } from '../storage';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { ExplorerSection } from './entities/explore-section';
|
||||
import { ExplorerService } from './services/explorer';
|
||||
export { ExplorerService } from './services/explorer';
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
import { filterPage } from '@affine/core/components/page-list';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import { ShareDocsListService } from '@affine/core/modules/share-doc';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
@@ -18,8 +20,6 @@ import { track } from '@affine/track';
|
||||
import type { DocMeta } from '@blocksuite/affine/store';
|
||||
import { FilterMinusIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocsService,
|
||||
GlobalContextService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
|
||||
@@ -9,7 +9,9 @@ import { useDeleteCollectionInfo } from '@affine/core/components/hooks/affine/us
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
@@ -19,12 +21,7 @@ import {
|
||||
PlusIcon,
|
||||
SplitViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { NodeOperation } from '../../tree/types';
|
||||
|
||||
@@ -7,15 +7,15 @@ import {
|
||||
} from '@affine/component';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
GlobalContextService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
|
||||
@@ -9,8 +9,11 @@ import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-pa
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
@@ -22,13 +25,7 @@ import {
|
||||
PlusIcon,
|
||||
SplitViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { NodeOperation } from '../../tree/types';
|
||||
|
||||
@@ -12,10 +12,12 @@ import {
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import {
|
||||
type FolderNode,
|
||||
OrganizeService,
|
||||
} from '@affine/core/modules/organize';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { Unreachable } from '@affine/env/constant';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -29,12 +31,7 @@ import {
|
||||
RemoveFolderIcon,
|
||||
TagsIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { difference } from 'lodash-es';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
|
||||
@@ -3,16 +3,13 @@ import {
|
||||
type DropTargetOptions,
|
||||
toast,
|
||||
} from '@affine/component';
|
||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||
import type { Tag } from '@affine/core/modules/tag';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { IconButton, MenuItem, MenuSeparator, toast } from '@affine/component';
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { FavoriteService } from '@affine/core/modules/favorite';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
@@ -12,13 +15,7 @@ import {
|
||||
PlusIcon,
|
||||
SplitViewIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import {
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { NodeOperation } from '../../tree/types';
|
||||
|
||||
@@ -13,16 +13,13 @@ import {
|
||||
FavoriteService,
|
||||
isFavoriteSupportType,
|
||||
} from '@affine/core/modules/favorite';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { isNewTabTrigger } from '@affine/core/utils';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { type MouseEventHandler, useCallback } from 'react';
|
||||
|
||||
import { ExplorerService } from '../../../services/explorer';
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
import { IconButton, useConfirmModal } from '@affine/component';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
||||
import { MigrationFavoriteItemsAdapter } from '@affine/core/modules/favorite';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
|
||||
import { DocsService, useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
type Framework,
|
||||
WorkspaceDBService,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceServerService } from '../cloud';
|
||||
import { WorkspaceDBService } from '../db';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { FavoriteList } from './entities/favorite-list';
|
||||
import { FavoriteService } from './services/favorite';
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
// the adapter is to bridge the workspace rootdoc & native js bindings
|
||||
import type { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { createYProxy, type Y } from '@blocksuite/affine/store';
|
||||
import type { WorkspaceService } from '@toeverything/infra';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
import { defaultsDeep } from 'lodash-es';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -155,6 +154,7 @@ class WorkspacePropertiesAdapter {
|
||||
setJournalPageDateString(id: string, date: string) {
|
||||
this.ensurePageProperties(id);
|
||||
const pageProperties = this.pageProperties?.[id];
|
||||
// oxlint-disable-next-line no-non-null-assertion
|
||||
pageProperties!.system[PageSystemPropertyId.Journal].value = date;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { WorkspaceDBService, WorkspaceService } from '@toeverything/infra';
|
||||
import { LiveData, Store } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { AuthService, type WorkspaceServerService } from '../../cloud';
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { FavoriteSupportTypeUnion } from '../constant';
|
||||
import { isFavoriteSupportType } from '../constant';
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import type { FlagInfo } from './types';
|
||||
|
||||
const isNotStableBuild = BUILD_CONFIG.appBuildType !== 'stable';
|
||||
const isDesktopEnvironment = BUILD_CONFIG.isElectron;
|
||||
const isCanaryBuild = BUILD_CONFIG.appBuildType === 'canary';
|
||||
const isMobile = BUILD_CONFIG.isMobileEdition;
|
||||
|
||||
export const AFFINE_FLAGS = {
|
||||
enable_ai: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai.description',
|
||||
hide: true,
|
||||
configurable: true,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_new_dnd: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_new_dnd',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-new-dnd.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-new-dnd.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_database_full_width: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_database_full_width',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-database-full-width.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-database-full-width.description',
|
||||
configurable: isCanaryBuild,
|
||||
},
|
||||
enable_database_attachment_note: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_database_attachment_note',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-database-attachment-note.description',
|
||||
configurable: isNotStableBuild,
|
||||
},
|
||||
enable_block_query: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_block_query',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-block-query.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-block-query.description',
|
||||
configurable: isCanaryBuild,
|
||||
},
|
||||
enable_synced_doc_block: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_synced_doc_block',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-synced-doc-block.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-synced-doc-block.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_edgeless_text: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_edgeless_text',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-edgeless-text.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-edgeless-text.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_color_picker: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_color_picker',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-color-picker.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-color-picker.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_ai_chat_block: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_ai_chat_block',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai-chat-block.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai-chat-block.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_ai_onboarding: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_ai_onboarding',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai-onboarding.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-ai-onboarding.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_mind_map_import: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_mind_map_import',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mind-map-import.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mind-map-import.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_multi_view: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multi-view.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multi-view.description',
|
||||
feedbackType: 'discord',
|
||||
feedbackLink:
|
||||
'https://discord.com/channels/959027316334407691/1280009690004324405',
|
||||
configurable: isDesktopEnvironment,
|
||||
defaultState: isCanaryBuild,
|
||||
},
|
||||
enable_emoji_folder_icon: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-emoji-folder-icon.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-emoji-folder-icon.description',
|
||||
|
||||
feedbackType: 'discord',
|
||||
feedbackLink:
|
||||
'https://discord.com/channels/959027316334407691/1280014319865696351/1280014319865696351',
|
||||
configurable: true,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_emoji_doc_icon: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-emoji-doc-icon.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-emoji-doc-icon.description',
|
||||
feedbackType: 'discord',
|
||||
feedbackLink:
|
||||
'https://discord.com/channels/959027316334407691/1280014319865696351',
|
||||
configurable: true,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_editor_settings: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-editor-settings.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-editor-settings.description',
|
||||
configurable: false,
|
||||
defaultState: true,
|
||||
},
|
||||
enable_offline_mode: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-offline-mode.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-offline-mode.description',
|
||||
configurable: isDesktopEnvironment,
|
||||
defaultState: false,
|
||||
},
|
||||
enable_theme_editor: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-theme-editor.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-theme-editor.description',
|
||||
configurable: isCanaryBuild && !isMobile,
|
||||
defaultState: isCanaryBuild,
|
||||
},
|
||||
enable_local_workspace: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-local-workspace.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-local-workspace.description',
|
||||
configurable: isCanaryBuild,
|
||||
defaultState: isDesktopEnvironment || isCanaryBuild,
|
||||
},
|
||||
enable_advanced_block_visibility: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_advanced_block_visibility',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-advanced-block-visibility.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-advanced-block-visibility.description',
|
||||
configurable: true,
|
||||
defaultState: false,
|
||||
},
|
||||
enable_mobile_keyboard_toolbar: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_mobile_keyboard_toolbar',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-keyboard-toolbar.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-keyboard-toolbar.description',
|
||||
configurable: false,
|
||||
defaultState: isMobile,
|
||||
},
|
||||
enable_mobile_linked_doc_menu: {
|
||||
category: 'blocksuite',
|
||||
bsFlag: 'enable_mobile_linked_doc_menu',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-linked-doc-menu.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-linked-doc-menu.description',
|
||||
configurable: false,
|
||||
defaultState: isMobile,
|
||||
},
|
||||
enable_multiple_cloud_servers: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-multiple-cloud-servers.description',
|
||||
configurable: isDesktopEnvironment,
|
||||
defaultState: false,
|
||||
},
|
||||
enable_mobile_edgeless_editing: {
|
||||
category: 'affine',
|
||||
displayName:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-edgeless-editing.name',
|
||||
description:
|
||||
'com.affine.settings.workspace.experimental-features.enable-mobile-edgeless-editing.description',
|
||||
configurable: isMobile,
|
||||
defaultState: false,
|
||||
},
|
||||
} satisfies { [key in string]: FlagInfo };
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { NEVER } from 'rxjs';
|
||||
|
||||
import type { GlobalStateService } from '../../storage';
|
||||
import { AFFINE_FLAGS } from '../constant';
|
||||
import type { FlagInfo } from '../types';
|
||||
|
||||
const FLAG_PREFIX = 'affine-flag:';
|
||||
|
||||
export type Flag<F extends FlagInfo = FlagInfo> = {
|
||||
readonly value: F['defaultState'] extends boolean
|
||||
? boolean
|
||||
: boolean | undefined;
|
||||
set: (value: boolean) => void;
|
||||
// eslint-disable-next-line rxjs/finnish
|
||||
$: F['defaultState'] extends boolean
|
||||
? LiveData<boolean>
|
||||
: LiveData<boolean> | LiveData<boolean | undefined>;
|
||||
} & F;
|
||||
|
||||
export class Flags extends Entity {
|
||||
private readonly globalState = this.globalStateService.globalState;
|
||||
|
||||
constructor(private readonly globalStateService: GlobalStateService) {
|
||||
super();
|
||||
|
||||
Object.entries(AFFINE_FLAGS).forEach(([flagKey, flag]) => {
|
||||
const configurable = flag.configurable ?? true;
|
||||
const defaultState =
|
||||
'defaultState' in flag ? flag.defaultState : undefined;
|
||||
const getValue = () => {
|
||||
return configurable
|
||||
? (this.globalState.get<boolean>(FLAG_PREFIX + flagKey) ??
|
||||
defaultState)
|
||||
: defaultState;
|
||||
};
|
||||
const item = {
|
||||
...flag,
|
||||
get value() {
|
||||
return getValue();
|
||||
},
|
||||
set: (value: boolean) => {
|
||||
if (!configurable) {
|
||||
return;
|
||||
}
|
||||
this.globalState.set(FLAG_PREFIX + flagKey, value);
|
||||
},
|
||||
$: configurable
|
||||
? LiveData.from<boolean | undefined>(
|
||||
this.globalState.watch<boolean>(FLAG_PREFIX + flagKey),
|
||||
undefined
|
||||
).map(value => value ?? defaultState)
|
||||
: LiveData.from(NEVER, defaultState),
|
||||
} as Flag<typeof flag>;
|
||||
Object.defineProperty(this, flagKey, {
|
||||
get: () => {
|
||||
return item;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type FlagsExt = Flags & {
|
||||
[K in keyof AFFINE_FLAGS]: Flag<AFFINE_FLAGS[K]>;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalStateService } from '../storage';
|
||||
import { Flags } from './entities/flags';
|
||||
import { FeatureFlagService } from './services/feature-flag';
|
||||
|
||||
export { AFFINE_FLAGS } from './constant';
|
||||
export type { Flag } from './entities/flags';
|
||||
export { FeatureFlagService } from './services/feature-flag';
|
||||
export type { FlagInfo } from './types';
|
||||
|
||||
export function configureFeatureFlagModule(framework: Framework) {
|
||||
framework.service(FeatureFlagService).entity(Flags, [GlobalStateService]);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
import { distinctUntilChanged, skip } from 'rxjs';
|
||||
|
||||
import { ApplicationStarted } from '../../lifecycle';
|
||||
import type { Workspace } from '../../workspace';
|
||||
import { WorkspaceInitialized } from '../../workspace/events';
|
||||
import { AFFINE_FLAGS } from '../constant';
|
||||
import { Flags, type FlagsExt } from '../entities/flags';
|
||||
|
||||
@OnEvent(WorkspaceInitialized, e => e.setupBlocksuiteEditorFlags)
|
||||
@OnEvent(ApplicationStarted, e => e.setupRestartListener)
|
||||
export class FeatureFlagService extends Service {
|
||||
flags = this.framework.createEntity(Flags) as FlagsExt;
|
||||
|
||||
setupBlocksuiteEditorFlags(workspace: Workspace) {
|
||||
for (const [key, flag] of Object.entries(AFFINE_FLAGS)) {
|
||||
if (flag.category === 'blocksuite') {
|
||||
const value = this.flags[key as keyof AFFINE_FLAGS].value;
|
||||
if (value !== undefined) {
|
||||
workspace.docCollection.awarenessStore.setFlag(flag.bsFlag, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupRestartListener() {
|
||||
this.flags.enable_ai.$.pipe(distinctUntilChanged(), skip(1)).subscribe(
|
||||
() => {
|
||||
// when enable_ai flag changes, reload the page.
|
||||
window.location.reload();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { BlockSuiteFlags } from '@blocksuite/affine/global/types';
|
||||
|
||||
type FeedbackType = 'discord' | 'email' | 'github';
|
||||
|
||||
export type FlagInfo = {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
configurable?: boolean;
|
||||
defaultState?: boolean; // default to open and not controlled by user
|
||||
/**
|
||||
* hide in the feature flag settings, but still can be controlled by the code
|
||||
*/
|
||||
hide?: boolean;
|
||||
feedbackType?: FeedbackType;
|
||||
feedbackLink?: string;
|
||||
} & (
|
||||
| {
|
||||
category: 'affine';
|
||||
}
|
||||
| {
|
||||
category: 'blocksuite';
|
||||
bsFlag: keyof BlockSuiteFlags;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import { Entity, LiveData, MemoryMemento } from '@toeverything/infra';
|
||||
|
||||
export class GlobalContext extends Entity {
|
||||
memento = new MemoryMemento();
|
||||
|
||||
workspaceId = this.define<string>('workspaceId');
|
||||
workspaceFlavour = this.define<string>('workspaceFlavour');
|
||||
|
||||
serverId = this.define<string>('serverId');
|
||||
|
||||
/**
|
||||
* is in doc page
|
||||
*/
|
||||
isDoc = this.define<boolean>('isDoc');
|
||||
isTrashDoc = this.define<boolean>('isTrashDoc');
|
||||
docId = this.define<string>('docId');
|
||||
docMode = this.define<DocMode>('docMode');
|
||||
|
||||
/**
|
||||
* is in collection page
|
||||
*/
|
||||
isCollection = this.define<boolean>('isCollection');
|
||||
collectionId = this.define<string>('collectionId');
|
||||
|
||||
/**
|
||||
* is in trash page
|
||||
*/
|
||||
isTrash = this.define<boolean>('isTrash');
|
||||
|
||||
/**
|
||||
* is in tag page
|
||||
*/
|
||||
isTag = this.define<boolean>('isTag');
|
||||
tagId = this.define<string>('tagId');
|
||||
|
||||
/**
|
||||
* is in all docs page
|
||||
*/
|
||||
isAllDocs = this.define<boolean>('isAllDocs');
|
||||
|
||||
define<T>(key: string) {
|
||||
this.memento.set(key, null);
|
||||
const livedata$ = LiveData.from(this.memento.watch<T>(key), null);
|
||||
return {
|
||||
get: () => this.memento.get(key) as T | null,
|
||||
set: (value: T | null) => this.memento.set(key, value),
|
||||
$: livedata$,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export { GlobalContextService } from './services/global-context';
|
||||
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalContext } from './entities/global-context';
|
||||
import { GlobalContextService } from './services/global-context';
|
||||
|
||||
export function configureGlobalContextModule(framework: Framework) {
|
||||
framework.service(GlobalContextService).entity(GlobalContext);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { GlobalContext } from '../entities/global-context';
|
||||
|
||||
export class GlobalContextService extends Service {
|
||||
globalContext = this.framework.createEntity(GlobalContext);
|
||||
}
|
||||
@@ -6,10 +6,11 @@ import {
|
||||
type Language,
|
||||
SUPPORTED_LANGUAGES,
|
||||
} from '@affine/i18n';
|
||||
import type { GlobalCache } from '@toeverything/infra';
|
||||
import { effect, Entity, fromPromise, LiveData } from '@toeverything/infra';
|
||||
import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
||||
|
||||
import type { GlobalCache } from '../../storage';
|
||||
|
||||
export type LanguageInfo = {
|
||||
key: Language;
|
||||
name: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type Framework, GlobalCache } from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalCache } from '../storage';
|
||||
import { I18nProvider } from './context';
|
||||
import { I18n, type LanguageInfo } from './entities/i18n';
|
||||
import { I18nService } from './services/i18n';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type Framework, WorkspacesService } from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { FetchService } from '../cloud';
|
||||
import { WorkspacesService } from '../workspace';
|
||||
import { ImportTemplateDialog } from './entities/dialog';
|
||||
import { TemplateDownloader } from './entities/downloader';
|
||||
import { TemplateDownloaderService } from './services/downloader';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks';
|
||||
import type { WorkspaceMetadata, WorkspacesService } from '@toeverything/infra';
|
||||
import { DocsService, Service } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { DocsService } from '../../doc';
|
||||
import type { WorkspaceMetadata, WorkspacesService } from '../../workspace';
|
||||
|
||||
export class ImportTemplateService extends Service {
|
||||
constructor(private readonly workspacesService: WorkspacesService) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { configureQuotaModule } from '@affine/core/modules/quota';
|
||||
import { configureInfraModules, type Framework } from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { configureAppSidebarModule } from './app-sidebar';
|
||||
import { configAtMenuConfigModule } from './at-menu-config';
|
||||
import { configureCloudModule } from './cloud';
|
||||
import { configureCollectionModule } from './collection';
|
||||
import { configureWorkspaceDBModule } from './db';
|
||||
import { configureDialogModule } from './dialogs';
|
||||
import { configureDndModule } from './dnd';
|
||||
import { configureDocModule } from './doc';
|
||||
import { configureDocDisplayMetaModule } from './doc-display-meta';
|
||||
import { configureDocInfoModule } from './doc-info';
|
||||
import { configureDocLinksModule } from './doc-link';
|
||||
@@ -15,9 +17,12 @@ import { configureEditorModule } from './editor';
|
||||
import { configureEditorSettingModule } from './editor-setting';
|
||||
import { configureExplorerModule } from './explorer';
|
||||
import { configureFavoriteModule } from './favorite';
|
||||
import { configureFeatureFlagModule } from './feature-flag';
|
||||
import { configureGlobalContextModule } from './global-context';
|
||||
import { configureI18nModule } from './i18n';
|
||||
import { configureImportTemplateModule } from './import-template';
|
||||
import { configureJournalModule } from './journal';
|
||||
import { configureLifecycleModule } from './lifecycle';
|
||||
import { configureNavigationModule } from './navigation';
|
||||
import { configureOpenInApp } from './open-in-app';
|
||||
import { configureOrganizeModule } from './organize';
|
||||
@@ -27,7 +32,10 @@ import { configurePermissionsModule } from './permissions';
|
||||
import { configureQuickSearchModule } from './quicksearch';
|
||||
import { configureShareDocsModule } from './share-doc';
|
||||
import { configureShareSettingModule } from './share-setting';
|
||||
import { configureCommonGlobalStorageImpls } from './storage';
|
||||
import {
|
||||
configureCommonGlobalStorageImpls,
|
||||
configureGlobalStorageModule,
|
||||
} from './storage';
|
||||
import { configureSystemFontFamilyModule } from './system-font-family';
|
||||
import { configureTagModule } from './tag';
|
||||
import { configureTelemetryModule } from './telemetry';
|
||||
@@ -35,10 +43,17 @@ import { configureAppThemeModule } from './theme';
|
||||
import { configureThemeEditorModule } from './theme-editor';
|
||||
import { configureUrlModule } from './url';
|
||||
import { configureUserspaceModule } from './userspace';
|
||||
import { configureWorkspaceModule } from './workspace';
|
||||
|
||||
export function configureCommonModules(framework: Framework) {
|
||||
configureI18nModule(framework);
|
||||
configureInfraModules(framework);
|
||||
configureWorkspaceModule(framework);
|
||||
configureDocModule(framework);
|
||||
configureWorkspaceDBModule(framework);
|
||||
configureGlobalStorageModule(framework);
|
||||
configureGlobalContextModule(framework);
|
||||
configureLifecycleModule(framework);
|
||||
configureFeatureFlagModule(framework);
|
||||
configureCollectionModule(framework);
|
||||
configureNavigationModule(framework);
|
||||
configureTagModule(framework);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
DocsService,
|
||||
type Framework,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { DocScope, DocService, DocsService } from '../doc';
|
||||
import { EditorSettingService } from '../editor-setting';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { JournalService } from './services/journal';
|
||||
import { JournalDocService } from './services/journal-doc';
|
||||
import { JournalStore } from './store/journal';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type DocService, Service } from '@toeverything/infra';
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { JournalService } from './journal';
|
||||
|
||||
export class JournalDocService extends Service {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Text } from '@blocksuite/affine/store';
|
||||
import type { DocProps, DocsService } from '@toeverything/infra';
|
||||
import type { DocProps } from '@toeverything/infra';
|
||||
import { initDocFromProps, LiveData, Service } from '@toeverything/infra';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import type { DocsService } from '../../doc';
|
||||
import type { EditorSettingService } from '../../editor-setting';
|
||||
import type { JournalStore } from '../store/journal';
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { DocsService } from '@toeverything/infra';
|
||||
import { LiveData, Store } from '@toeverything/infra';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
import type { DocsService } from '../../doc';
|
||||
|
||||
function isJournalString(j?: string | false) {
|
||||
return j ? !!j?.match(/^\d{4}-\d{2}-\d{2}$/) : false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { LifecycleService } from './service/lifecycle';
|
||||
|
||||
export {
|
||||
ApplicationFocused,
|
||||
ApplicationStarted,
|
||||
LifecycleService,
|
||||
} from './service/lifecycle';
|
||||
|
||||
export function configureLifecycleModule(framework: Framework) {
|
||||
framework.service(LifecycleService);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createEvent, Service } from '@toeverything/infra';
|
||||
|
||||
/**
|
||||
* Event that is emitted when application is started.
|
||||
*/
|
||||
export const ApplicationStarted = createEvent<boolean>('ApplicationStartup');
|
||||
|
||||
/**
|
||||
* Event that is emitted when browser tab or windows is focused again, after being blurred.
|
||||
* Can be used to actively refresh some data.
|
||||
*/
|
||||
export const ApplicationFocused = createEvent<boolean>('ApplicationFocused');
|
||||
|
||||
export class LifecycleService extends Service {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
applicationStart() {
|
||||
this.eventBus.emit(ApplicationStarted, true);
|
||||
}
|
||||
|
||||
applicationFocus() {
|
||||
this.eventBus.emit(ApplicationFocused, true);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,10 @@ export {
|
||||
} from './utils';
|
||||
export { NavigationButtons } from './view/navigation-buttons';
|
||||
|
||||
import { type Framework, WorkspaceScope } from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkbenchService } from '../workbench/services/workbench';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { Navigator } from './entities/navigator';
|
||||
import { NavigatorService } from './services/navigator';
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
type Framework,
|
||||
GlobalState,
|
||||
WorkspacesService,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { GlobalState } from '../storage';
|
||||
import { WorkspacesService } from '../workspace';
|
||||
import { OpenInAppService } from './services';
|
||||
|
||||
export { OpenInAppService, OpenLinkMode } from './services';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { GlobalState, WorkspacesService } from '@toeverything/infra';
|
||||
import { LiveData, OnEvent, Service } from '@toeverything/infra';
|
||||
|
||||
import { resolveLinkToDoc } from '../../navigation';
|
||||
import type { GlobalState } from '../../storage';
|
||||
import { WorkbenchLocationChanged } from '../../workbench/services/workbench';
|
||||
import type { WorkspacesService } from '../../workspace';
|
||||
import { getLocalWorkspaceIds } from '../../workspace-engine/impls/local';
|
||||
|
||||
const storageKey = 'open-link-mode';
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
type Framework,
|
||||
WorkspaceDBService,
|
||||
WorkspaceScope,
|
||||
} from '@toeverything/infra';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDBService } from '../db';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { FolderNode } from './entities/folder-node';
|
||||
import { FolderTree } from './entities/folder-tree';
|
||||
import { OrganizeService } from './services/organize';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { WorkspaceDBService } from '@toeverything/infra';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
|
||||
export class FolderStore extends Store {
|
||||
constructor(private readonly dbService: WorkspaceDBService) {
|
||||
super();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user