feat(core): template doc setting ui (#9658)

close AF-2051, AF-2052
This commit is contained in:
CatsJuice
2025-01-14 02:10:34 +00:00
parent 57b89b5ad4
commit af3ad4bfe9
18 changed files with 481 additions and 206 deletions

View File

@@ -10,3 +10,23 @@ export const root = style({
borderColor: cssVarV2('layer/insideBorder/border'),
background: cssVarV2('button/siderbarPrimary/background'),
});
export const withAskRoot = style([
root,
{
width: 'auto',
padding: 6,
},
]);
export const withAskContent = style({
fontSize: 20,
display: 'flex',
alignItems: 'center',
gap: 2,
color: cssVarV2.icon.primary,
});
export const templateMenu = style({
width: 280,
});

View File

@@ -1,34 +1,190 @@
import { IconButton } from '@affine/component';
import { Button, IconButton, Menu, MenuItem, MenuSub } from '@affine/component';
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocsService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import {
TemplateDocService,
TemplateListMenuContentScrollable,
} from '@affine/core/modules/template-doc';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { inferOpenMode } from '@affine/core/utils';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { PlusIcon } from '@blocksuite/icons/rc';
import { useService } from '@toeverything/infra';
import type { DocMode } from '@blocksuite/affine/blocks';
import {
ArrowDownSmallIcon,
EdgelessIcon,
PageIcon,
PlusIcon,
TemplateIcon,
} from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import type React from 'react';
import { type MouseEvent, useCallback } from 'react';
import * as styles from './index.css';
/**
* @return a function to create a new doc, will duplicate the template doc if the page template is enabled
*/
const useNewDoc = () => {
const featureFlagService = useService(FeatureFlagService);
const workspaceService = useService(WorkspaceService);
const templateDocService = useService(TemplateDocService);
const docsService = useService(DocsService);
const workbench = useService(WorkbenchService).workbench;
const enableTemplateDoc = useLiveData(
featureFlagService.flags.enable_template_doc.$
);
const currentWorkspace = workspaceService.workspace;
const enablePageTemplate = useLiveData(
templateDocService.setting.enablePageTemplate$
);
const pageTemplateDocId = useLiveData(
templateDocService.setting.pageTemplateDocId$
);
const pageHelper = usePageHelper(currentWorkspace.docCollection);
const createPage = useAsyncCallback(
async (e?: MouseEvent, mode?: DocMode) => {
if (enableTemplateDoc && enablePageTemplate && pageTemplateDocId) {
const docId =
await docsService.duplicateFromTemplate(pageTemplateDocId);
workbench.openDoc(docId, { at: inferOpenMode(e) });
} else {
pageHelper.createPage(mode, { at: inferOpenMode(e) });
}
},
[
docsService,
enablePageTemplate,
enableTemplateDoc,
pageHelper,
pageTemplateDocId,
workbench,
]
);
return createPage;
};
interface AddPageButtonProps {
className?: string;
style?: React.CSSProperties;
}
const sideBottom = { side: 'bottom' as const };
export function AddPageButton({ className, style }: AddPageButtonProps) {
const workspaceService = useService(WorkspaceService);
const currentWorkspace = workspaceService.workspace;
const pageHelper = usePageHelper(currentWorkspace.docCollection);
export function AddPageButton(props: AddPageButtonProps) {
const editorSetting = useService(EditorSettingService);
const newDocDefaultMode = useLiveData(
editorSetting.editorSetting.settings$.selector(s => s.newDocDefaultMode)
);
return newDocDefaultMode === 'ask' ? (
<AddPageWithAsk {...props} />
) : (
<AddPageWithoutAsk {...props} />
);
}
function AddPageWithAsk({ className, style }: AddPageButtonProps) {
const t = useI18n();
const createDoc = useNewDoc();
const workbench = useService(WorkbenchService).workbench;
const docsService = useService(DocsService);
const createPage = useCallback(
(e?: MouseEvent) => {
createDoc(e, 'page');
track.$.navigationPanel.$.createDoc();
},
[createDoc]
);
const createEdgeless = useCallback(
(e?: MouseEvent) => {
createDoc(e, 'edgeless');
track.$.navigationPanel.$.createDoc();
},
[createDoc]
);
const createDocFromTemplate = useAsyncCallback(
async (templateId: string) => {
const docId = await docsService.duplicateFromTemplate(templateId);
workbench.openDoc(docId);
},
[docsService, workbench]
);
return (
<Menu
items={
<>
<MenuItem
prefixIcon={<PageIcon />}
onClick={createPage}
onAuxClick={createPage}
>
{t['Page']()}
</MenuItem>
<MenuItem
prefixIcon={<EdgelessIcon />}
onClick={createEdgeless}
onAuxClick={createEdgeless}
>
{t['Edgeless']()}
</MenuItem>
<MenuSub
triggerOptions={{
prefixIcon: <TemplateIcon />,
}}
subContentOptions={{
sideOffset: 16,
className: styles.templateMenu,
}}
items={
<TemplateListMenuContentScrollable
onSelect={createDocFromTemplate}
/>
}
>
{t['Template']()}
</MenuSub>
</>
}
>
<Button
tooltip={t['New Page']()}
tooltipOptions={sideBottom}
data-testid="sidebar-new-page-button"
className={clsx([styles.withAskRoot, className])}
style={style}
>
<div className={styles.withAskContent}>
<PlusIcon />
<ArrowDownSmallIcon />
</div>
</Button>
</Menu>
);
}
function AddPageWithoutAsk({ className, style }: AddPageButtonProps) {
const createDoc = useNewDoc();
const onClickNewPage = useCallback(
(e?: MouseEvent) => {
pageHelper.createPage(undefined, { at: inferOpenMode(e) });
createDoc(e);
track.$.navigationPanel.$.createDoc();
},
[pageHelper]
[createDoc]
);
const t = useI18n();