feat(core): sidebar template doc entrance (#9676)

close AF-2048, AF-2050, AF-2049
This commit is contained in:
CatsJuice
2025-01-15 03:45:10 +00:00
parent 1c90747899
commit 419611f44c
4 changed files with 96 additions and 0 deletions

View File

@@ -46,6 +46,7 @@ import {
workspaceWrapper,
} from './index.css';
import { AppSidebarJournalButton } from './journal-button';
import { TemplateDocEntrance } from './template-doc-entrance';
import { TrashButton } from './trash-button';
import { UpdaterButton } from './updater-button';
import { UserInfo } from './user-info';
@@ -192,6 +193,7 @@ export const RootAppSidebar = memo((): ReactElement => {
>
<span data-testid="import-modal-trigger">{t['Import']()}</span>
</MenuItem>
<TemplateDocEntrance />
<ExternalMenuLinkItem
href="https://affine.pro/blog?tag=Release+Note"
icon={<JournalIcon />}

View File

@@ -0,0 +1,89 @@
import { Menu, MenuItem, MenuSeparator } from '@affine/component';
import { MenuItem as SidebarMenuItem } from '@affine/core/modules/app-sidebar/views';
import { DocsService } from '@affine/core/modules/doc';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { TemplateListMenuContentScrollable } from '@affine/core/modules/template-doc';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { inferOpenMode } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import { TemplateIcon, TemplateOutlineIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useState } from 'react';
import { useAsyncCallback } from '../hooks/affine-async-hooks';
export const TemplateDocEntrance = () => {
const t = useI18n();
const [menuOpen, setMenuOpen] = useState(false);
const docsService = useService(DocsService);
const featureFlagService = useService(FeatureFlagService);
const workbench = useService(WorkbenchService).workbench;
const enabled = useLiveData(featureFlagService.flags.enable_template_doc.$);
const toggleMenu = useCallback(() => {
setMenuOpen(prev => !prev);
}, []);
const createDocFromTemplate = useAsyncCallback(
async (templateId: string) => {
const docId = await docsService.duplicateFromTemplate(templateId);
workbench.openDoc(docId);
},
[docsService, workbench]
);
if (!enabled) {
return null;
}
return (
<SidebarMenuItem icon={<TemplateOutlineIcon />} onClick={toggleMenu}>
<Menu
rootOptions={{ open: menuOpen, onOpenChange: setMenuOpen }}
contentOptions={{
side: 'right',
align: 'end',
alignOffset: -4,
sideOffset: 16,
}}
items={
<TemplateListMenuContentScrollable
onSelect={createDocFromTemplate}
suffixItems={
<>
<MenuSeparator />
<CreateNewTemplateMenuItem />
</>
}
/>
}
>
<span>{t['Template']()}</span>
</Menu>
</SidebarMenuItem>
);
};
const CreateNewTemplateMenuItem = () => {
const t = useI18n();
const docsService = useService(DocsService);
const workbench = useService(WorkbenchService).workbench;
const createNewTemplate = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
const record = docsService.createDoc({ isTemplate: true });
workbench.openDoc(record.id, { at: inferOpenMode(e) });
},
[docsService, workbench]
);
return (
<MenuItem
prefixIcon={<TemplateIcon />}
onClick={createNewTemplate}
onAuxClick={createNewTemplate}
>
{t['com.affine.template-list.create-new']()}
</MenuItem>
);
};

View File

@@ -102,6 +102,7 @@ export class DocsService extends Service {
options: {
primaryMode?: DocMode;
docProps?: DocProps;
isTemplate?: boolean;
} = {}
) {
const doc = this.store.createBlockSuiteDoc();
@@ -114,6 +115,9 @@ export class DocsService extends Service {
if (options.primaryMode) {
docRecord.setPrimaryMode(options.primaryMode);
}
if (options.isTemplate) {
docRecord.setProperty('isTemplate', true);
}
this.eventBus.emit(DocCreated, docRecord);
return docRecord;
}