diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts
new file mode 100644
index 000000000..87713f6d1
--- /dev/null
+++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts
@@ -0,0 +1,19 @@
+import { style } from '@vanilla-extract/css';
+
+export const menuItem = style({
+ display: 'flex',
+ alignItems: 'center',
+ gap: 8,
+});
+export const menuItemIcon = style({
+ fontSize: 24,
+ lineHeight: 0,
+});
+export const menuItemText = style({
+ fontSize: 14,
+ width: 0,
+ flex: 1,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+});
diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx
new file mode 100644
index 000000000..de9450303
--- /dev/null
+++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx
@@ -0,0 +1,110 @@
+import { Button, Menu, MenuItem } from '@affine/component';
+import { type DocRecord, DocsService } from '@affine/core/modules/doc';
+import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
+import { FeatureFlagService } from '@affine/core/modules/feature-flag';
+import { TemplateDocService } from '@affine/core/modules/template-doc';
+import { useLiveData, useService, useServices } from '@toeverything/infra';
+import { useCallback, useState } from 'react';
+
+import * as styles from './template.css';
+
+export const TemplateDocSetting = () => {
+ const { featureFlagService, templateDocService } = useServices({
+ FeatureFlagService,
+ TemplateDocService,
+ });
+ const setting = templateDocService.setting;
+
+ const enabled = useLiveData(featureFlagService.flags.enable_template_doc.$);
+ const loading = useLiveData(setting.loading$);
+ const pageTemplateDocId = useLiveData(setting.pageTemplateDocId$);
+ const journalTemplateDocId = useLiveData(setting.journalTemplateDocId$);
+
+ const updatePageTemplate = useCallback(
+ (id?: string) => {
+ setting.updatePageTemplateDocId(id);
+ },
+ [setting]
+ );
+
+ const updateJournalTemplate = useCallback(
+ (id?: string) => {
+ setting.updateJournalTemplateDocId(id);
+ },
+ [setting]
+ );
+
+ if (!enabled) return null;
+ if (loading) return null;
+
+ return (
+
+ Normal Page:
+
+
+ Journal:
+
+
+ );
+};
+
+interface TemplateSelectorProps {
+ current?: string;
+ onChange?: (id?: string) => void;
+}
+const TemplateSelector = ({ current, onChange }: TemplateSelectorProps) => {
+ const docsService = useService(DocsService);
+ const doc = useLiveData(current ? docsService.list.doc$(current) : null);
+ const isInTrash = useLiveData(doc?.trash$);
+
+ return (
+ }>
+
+
+ );
+};
+
+const List = ({ onChange }: { onChange?: (id?: string) => void }) => {
+ const list = useService(TemplateDocService).list;
+ const [docs] = useState(list.getTemplateDocs());
+
+ const handleClick = useCallback(
+ (id: string) => {
+ onChange?.(id);
+ },
+ [onChange]
+ );
+
+ return docs.map(doc => {
+ return ;
+ });
+};
+
+interface DocItemProps {
+ doc: DocRecord;
+ onClick?: (id: string) => void;
+}
+const DocItem = ({ doc, onClick }: DocItemProps) => {
+ const docDisplayService = useService(DocDisplayMetaService);
+ const Icon = useLiveData(docDisplayService.icon$(doc.id));
+ const title = useLiveData(docDisplayService.title$(doc.id));
+
+ const handleClick = useCallback(() => {
+ onClick?.(doc.id);
+ }, [doc.id, onClick]);
+
+ return (
+
+ );
+};
diff --git a/packages/frontend/core/src/modules/db/index.ts b/packages/frontend/core/src/modules/db/index.ts
index 7fc1c5ceb..203fff902 100644
--- a/packages/frontend/core/src/modules/db/index.ts
+++ b/packages/frontend/core/src/modules/db/index.ts
@@ -1,5 +1,6 @@
import type { Framework } from '@toeverything/infra';
+import { WorkspaceServerService } from '../cloud';
import { WorkspaceScope, WorkspaceService } from '../workspace';
import { WorkspaceDB } from './entities/db';
import { WorkspaceDBTable } from './entities/table';
@@ -11,7 +12,7 @@ export { WorkspaceDBService } from './services/db';
export function configureWorkspaceDBModule(framework: Framework) {
framework
.scope(WorkspaceScope)
- .service(WorkspaceDBService, [WorkspaceService])
+ .service(WorkspaceDBService, [WorkspaceService, WorkspaceServerService])
.entity(WorkspaceDB)
.entity(WorkspaceDBTable, [WorkspaceService]);
}
diff --git a/packages/frontend/core/src/modules/db/schema/schema.ts b/packages/frontend/core/src/modules/db/schema/schema.ts
index 3d17513d9..746dd75bb 100644
--- a/packages/frontend/core/src/modules/db/schema/schema.ts
+++ b/packages/frontend/core/src/modules/db/schema/schema.ts
@@ -48,6 +48,10 @@ export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = {
key: f.string().primaryKey(),
index: f.string(),
},
+ settings: {
+ key: f.string().primaryKey(),
+ value: f.json(),
+ },
} as const satisfies DBSchemaBuilder;
export type AFFiNEWorkspaceUserdataDbSchema =
typeof AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA;
diff --git a/packages/frontend/core/src/modules/db/services/db.ts b/packages/frontend/core/src/modules/db/services/db.ts
index 41138c5d8..ed9d8dd8d 100644
--- a/packages/frontend/core/src/modules/db/services/db.ts
+++ b/packages/frontend/core/src/modules/db/services/db.ts
@@ -1,11 +1,13 @@
import {
createORMClient,
+ LiveData,
ObjectPool,
Service,
YjsDBAdapter,
} from '@toeverything/infra';
import { Doc as YDoc } from 'yjs';
+import { AuthService, type WorkspaceServerService } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db';
import {
@@ -31,7 +33,10 @@ export class WorkspaceDBService extends Service {
},
});
- constructor(private readonly workspaceService: WorkspaceService) {
+ constructor(
+ private readonly workspaceService: WorkspaceService,
+ private readonly workspaceServerService: WorkspaceServerService
+ ) {
super();
this.db = this.framework.createEntity(
WorkspaceDB,
@@ -95,6 +100,25 @@ export class WorkspaceDBService extends Service {
return newDB as WorkspaceDBWithTables;
}
+ authService = this.workspaceServerService.server?.scope.get(AuthService);
+ public get userdataDB$() {
+ // if is local workspace or no account, use __local__ userdata
+ // sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
+ if (
+ this.workspaceService.workspace.meta.flavour === 'local' ||
+ !this.authService
+ ) {
+ return new LiveData(this.userdataDB('__local__'));
+ } else {
+ return this.authService.session.account$.map(account => {
+ if (!account) {
+ return this.userdataDB('__local__');
+ }
+ return this.userdataDB(account.id);
+ });
+ }
+ }
+
static isDBDocId(docId: string) {
return docId.startsWith('db$') || docId.startsWith('userdata$');
}
diff --git a/packages/frontend/core/src/modules/favorite/index.ts b/packages/frontend/core/src/modules/favorite/index.ts
index b5eae5315..2bd08bfb0 100644
--- a/packages/frontend/core/src/modules/favorite/index.ts
+++ b/packages/frontend/core/src/modules/favorite/index.ts
@@ -1,6 +1,5 @@
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';
@@ -28,11 +27,7 @@ export function configureFavoriteModule(framework: Framework) {
.scope(WorkspaceScope)
.service(FavoriteService)
.entity(FavoriteList, [FavoriteStore])
- .store(FavoriteStore, [
- WorkspaceDBService,
- WorkspaceService,
- WorkspaceServerService,
- ])
+ .store(FavoriteStore, [WorkspaceDBService])
.service(MigrationFavoriteItemsAdapter, [WorkspaceService])
.service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
}
diff --git a/packages/frontend/core/src/modules/favorite/stores/favorite.ts b/packages/frontend/core/src/modules/favorite/stores/favorite.ts
index c4ca68453..1d79a76a8 100644
--- a/packages/frontend/core/src/modules/favorite/stores/favorite.ts
+++ b/packages/frontend/core/src/modules/favorite/stores/favorite.ts
@@ -1,9 +1,7 @@
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';
@@ -14,41 +12,18 @@ export interface FavoriteRecord {
}
export class FavoriteStore extends Store {
- authService = this.workspaceServerService.server?.scope.get(AuthService);
- constructor(
- private readonly workspaceDBService: WorkspaceDBService,
- private readonly workspaceService: WorkspaceService,
- private readonly workspaceServerService: WorkspaceServerService
- ) {
+ constructor(private readonly workspaceDBService: WorkspaceDBService) {
super();
}
- private get userdataDB$() {
- // if is local workspace or no account, use __local__ userdata
- // sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
- if (
- this.workspaceService.workspace.meta.flavour === 'local' ||
- !this.authService
- ) {
- return new LiveData(this.workspaceDBService.userdataDB('__local__'));
- } else {
- return this.authService.session.account$.map(account => {
- if (!account) {
- return this.workspaceDBService.userdataDB('__local__');
- }
- return this.workspaceDBService.userdataDB(account.id);
- });
- }
- }
-
watchIsLoading() {
- return this.userdataDB$
+ return this.workspaceDBService.userdataDB$
.map(db => LiveData.from(db.favorite.isLoading$, false))
.flat();
}
watchFavorites() {
- return this.userdataDB$
+ return this.workspaceDBService.userdataDB$
.map(db => LiveData.from(db.favorite.find$(), []))
.flat()
.map(raw => {
@@ -63,7 +38,7 @@ export class FavoriteStore extends Store {
id: string,
index: string
): FavoriteRecord {
- const db = this.userdataDB$.value;
+ const db = this.workspaceDBService.userdataDB$.value;
const raw = db.favorite.create({
key: this.encodeKey(type, id),
index,
@@ -72,17 +47,17 @@ export class FavoriteStore extends Store {
}
reorderFavorite(type: FavoriteSupportTypeUnion, id: string, index: string) {
- const db = this.userdataDB$.value;
+ const db = this.workspaceDBService.userdataDB$.value;
db.favorite.update(this.encodeKey(type, id), { index });
}
removeFavorite(type: FavoriteSupportTypeUnion, id: string) {
- const db = this.userdataDB$.value;
+ const db = this.workspaceDBService.userdataDB$.value;
db.favorite.delete(this.encodeKey(type, id));
}
watchFavorite(type: FavoriteSupportTypeUnion, id: string) {
- const db = this.userdataDB$.value;
+ const db = this.workspaceDBService.userdataDB$.value;
return LiveData.from(
db.favorite
.get$(this.encodeKey(type, id))
diff --git a/packages/frontend/core/src/modules/template-doc/entities/setting.ts b/packages/frontend/core/src/modules/template-doc/entities/setting.ts
index 746a6e49b..7fbcb0ea1 100644
--- a/packages/frontend/core/src/modules/template-doc/entities/setting.ts
+++ b/packages/frontend/core/src/modules/template-doc/entities/setting.ts
@@ -1,12 +1,22 @@
import { Entity } from '@toeverything/infra';
-export type TemplateDocSettings = {
- templateId?: string;
- journalTemplateId?: string;
-};
+import type { TemplateDocSettingStore } from '../store/setting';
export class TemplateDocSetting extends Entity {
- constructor() {
+ constructor(private readonly store: TemplateDocSettingStore) {
super();
}
+
+ loading$ = this.store.watchIsLoading();
+ setting$ = this.store.watchSetting();
+ pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId');
+ journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId');
+
+ updatePageTemplateDocId(id?: string) {
+ this.store.updateSetting('pageTemplateId', id);
+ }
+
+ updateJournalTemplateDocId(id?: string) {
+ this.store.updateSetting('journalTemplateId', id);
+ }
}
diff --git a/packages/frontend/core/src/modules/template-doc/index.ts b/packages/frontend/core/src/modules/template-doc/index.ts
index f05ca5e6a..4d4a42192 100644
--- a/packages/frontend/core/src/modules/template-doc/index.ts
+++ b/packages/frontend/core/src/modules/template-doc/index.ts
@@ -7,6 +7,7 @@ import { TemplateDocList } from './entities/list';
import { TemplateDocSetting } from './entities/setting';
import { TemplateDocService } from './services/template-doc';
import { TemplateDocListStore } from './store/list';
+import { TemplateDocSettingStore } from './store/setting';
export { TemplateDocService };
export * from './view/template-list-menu';
@@ -17,5 +18,6 @@ export const configureTemplateDocModule = (framework: Framework) => {
.service(TemplateDocService)
.store(TemplateDocListStore, [WorkspaceDBService])
.entity(TemplateDocList, [TemplateDocListStore, DocsService])
- .entity(TemplateDocSetting);
+ .store(TemplateDocSettingStore, [WorkspaceDBService])
+ .entity(TemplateDocSetting, [TemplateDocSettingStore]);
};
diff --git a/packages/frontend/core/src/modules/template-doc/store/setting.ts b/packages/frontend/core/src/modules/template-doc/store/setting.ts
new file mode 100644
index 000000000..b56f2149f
--- /dev/null
+++ b/packages/frontend/core/src/modules/template-doc/store/setting.ts
@@ -0,0 +1,47 @@
+import { LiveData, Store } from '@toeverything/infra';
+
+import type { WorkspaceDBService } from '../../db';
+import type { TemplateDocSettings } from '../type';
+
+export class TemplateDocSettingStore extends Store {
+ private readonly key = 'templateDoc';
+
+ constructor(private readonly dbService: WorkspaceDBService) {
+ super();
+ }
+
+ watchIsLoading() {
+ return this.dbService.userdataDB$
+ .map(db => LiveData.from(db.settings.isLoading$, false))
+ .flat();
+ }
+
+ watchSetting() {
+ return this.dbService.userdataDB$
+ .map(db => LiveData.from(db.settings.find$({ key: this.key }), []))
+ .flat()
+ .map(raw => raw?.[0]?.value as TemplateDocSettings);
+ }
+
+ watchSettingKey(key: T) {
+ return this.dbService.userdataDB$
+ .map(db => LiveData.from(db.settings.find$({ key: this.key }), []))
+ .flat()
+ .map(raw => {
+ const value = raw?.[0]?.value as TemplateDocSettings;
+ if (!value) return undefined;
+ return value[key];
+ });
+ }
+
+ updateSetting(
+ key: T,
+ value: TemplateDocSettings[T]
+ ) {
+ const db = this.dbService.userdataDB$.value;
+ const prev = db.settings.find({ key: this.key })[0]?.value ?? {};
+ db.settings.update(this.key, {
+ value: { ...prev, [key]: value },
+ });
+ }
+}
diff --git a/packages/frontend/core/src/modules/template-doc/type.ts b/packages/frontend/core/src/modules/template-doc/type.ts
new file mode 100644
index 000000000..d7a288a25
--- /dev/null
+++ b/packages/frontend/core/src/modules/template-doc/type.ts
@@ -0,0 +1,4 @@
+export type TemplateDocSettings = {
+ pageTemplateId?: string;
+ journalTemplateId?: string;
+};