mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-24 05:07:06 +08:00
+11
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import {
|
import {
|
||||||
TemplateDocService,
|
TemplateDocService,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import { useAsyncCallback } from '../../hooks/affine-async-hooks';
|
||||||
import * as styles from './starter-bar.css';
|
import * as styles from './starter-bar.css';
|
||||||
|
|
||||||
const Badge = forwardRef<
|
const Badge = forwardRef<
|
||||||
@@ -48,6 +50,7 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
|
|||||||
|
|
||||||
const templateDocService = useService(TemplateDocService);
|
const templateDocService = useService(TemplateDocService);
|
||||||
const featureFlagService = useService(FeatureFlagService);
|
const featureFlagService = useService(FeatureFlagService);
|
||||||
|
const docsService = useService(DocsService);
|
||||||
|
|
||||||
const [templateMenuOpen, setTemplateMenuOpen] = useState(false);
|
const [templateMenuOpen, setTemplateMenuOpen] = useState(false);
|
||||||
|
|
||||||
@@ -61,6 +64,13 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
|
|||||||
featureFlagService.flags.enable_template_doc.$
|
featureFlagService.flags.enable_template_doc.$
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSelectTemplate = useAsyncCallback(
|
||||||
|
async (templateId: string) => {
|
||||||
|
await docsService.duplicateFromTemplate(templateId, doc.id);
|
||||||
|
},
|
||||||
|
[doc.id, docsService]
|
||||||
|
);
|
||||||
|
|
||||||
const showAI = false;
|
const showAI = false;
|
||||||
const showEdgeless = false;
|
const showEdgeless = false;
|
||||||
const showTemplate = !isTemplate && enableTemplateDoc;
|
const showTemplate = !isTemplate && enableTemplateDoc;
|
||||||
@@ -82,7 +92,7 @@ const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
|
|||||||
|
|
||||||
{showTemplate ? (
|
{showTemplate ? (
|
||||||
<TemplateListMenu
|
<TemplateListMenu
|
||||||
target={doc.id}
|
onSelect={handleSelectTemplate}
|
||||||
rootOptions={{
|
rootOptions={{
|
||||||
open: templateMenuOpen,
|
open: templateMenuOpen,
|
||||||
onOpenChange: setTemplateMenuOpen,
|
onOpenChange: setTemplateMenuOpen,
|
||||||
|
|||||||
+46
-22
@@ -2,6 +2,7 @@ import {
|
|||||||
Loading,
|
Loading,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
type MenuProps,
|
||||||
MenuSeparator,
|
MenuSeparator,
|
||||||
MenuTrigger,
|
MenuTrigger,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
@@ -19,6 +20,7 @@ import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hoo
|
|||||||
import { ServerService } from '@affine/core/modules/cloud';
|
import { ServerService } from '@affine/core/modules/cloud';
|
||||||
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||||
import {
|
import {
|
||||||
|
type EditorSettingSchema,
|
||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
type FontFamily,
|
type FontFamily,
|
||||||
fontStyleOptions,
|
fontStyleOptions,
|
||||||
@@ -30,7 +32,6 @@ import {
|
|||||||
SystemFontFamilyService,
|
SystemFontFamilyService,
|
||||||
} from '@affine/core/modules/system-font-family';
|
} from '@affine/core/modules/system-font-family';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
|
||||||
import { DoneIcon, SearchIcon } from '@blocksuite/icons/rc';
|
import { DoneIcon, SearchIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -306,27 +307,39 @@ const CustomFontFamilySettings = () => {
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const menuContentOptions: MenuProps['contentOptions'] = {
|
||||||
|
align: 'end',
|
||||||
|
sideOffset: 16,
|
||||||
|
style: { width: 250 },
|
||||||
|
};
|
||||||
const NewDocDefaultModeSettings = () => {
|
const NewDocDefaultModeSettings = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { editorSettingService } = useServices({ EditorSettingService });
|
const { editorSettingService } = useServices({ EditorSettingService });
|
||||||
const settings = useLiveData(editorSettingService.editorSetting.settings$);
|
const settings = useLiveData(editorSettingService.editorSetting.settings$);
|
||||||
const radioItems = useMemo<RadioItem[]>(
|
const items = useMemo(
|
||||||
() => [
|
() =>
|
||||||
{
|
[
|
||||||
value: 'page',
|
{
|
||||||
label: t['Page'](),
|
value: 'page',
|
||||||
testId: 'page-mode-trigger',
|
label: t['Page'](),
|
||||||
},
|
testId: 'page-mode-trigger',
|
||||||
{
|
},
|
||||||
value: 'edgeless',
|
{
|
||||||
label: t['Edgeless'](),
|
value: 'edgeless',
|
||||||
testId: 'edgeless-mode-trigger',
|
label: t['Edgeless'](),
|
||||||
},
|
testId: 'edgeless-mode-trigger',
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
value: 'ask',
|
||||||
|
label: 'Ask every time',
|
||||||
|
testId: 'ask-every-time-trigger',
|
||||||
|
},
|
||||||
|
] as const,
|
||||||
[t]
|
[t]
|
||||||
);
|
);
|
||||||
const updateNewDocDefaultMode = useCallback(
|
const updateNewDocDefaultMode = useCallback(
|
||||||
(value: DocMode) => {
|
(value: EditorSettingSchema['newDocDefaultMode']) => {
|
||||||
editorSettingService.editorSetting.set('newDocDefaultMode', value);
|
editorSettingService.editorSetting.set('newDocDefaultMode', value);
|
||||||
},
|
},
|
||||||
[editorSettingService.editorSetting]
|
[editorSettingService.editorSetting]
|
||||||
@@ -340,13 +353,24 @@ const NewDocDefaultModeSettings = () => {
|
|||||||
'com.affine.settings.editorSettings.general.default-new-doc.description'
|
'com.affine.settings.editorSettings.general.default-new-doc.description'
|
||||||
]()}
|
]()}
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<Menu
|
||||||
items={radioItems}
|
contentOptions={menuContentOptions}
|
||||||
value={settings.newDocDefaultMode}
|
items={items.map(item => {
|
||||||
width={250}
|
return (
|
||||||
className={styles.settingWrapper}
|
<MenuItem
|
||||||
onChange={updateNewDocDefaultMode}
|
key={item.value}
|
||||||
/>
|
selected={item.value === settings.newDocDefaultMode}
|
||||||
|
onSelect={() => updateNewDocDefaultMode(item.value)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuTrigger className={styles.menuTrigger}>
|
||||||
|
{items.find(item => item.value === settings.newDocDefaultMode)?.label}
|
||||||
|
</MenuTrigger>
|
||||||
|
</Menu>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+2
@@ -19,6 +19,7 @@ import { LabelsPanel } from './labels';
|
|||||||
import { MembersPanel } from './members';
|
import { MembersPanel } from './members';
|
||||||
import { ProfilePanel } from './profile';
|
import { ProfilePanel } from './profile';
|
||||||
import { SharingPanel } from './sharing';
|
import { SharingPanel } from './sharing';
|
||||||
|
import { TemplateDocSetting } from './template';
|
||||||
import type { WorkspaceSettingDetailProps } from './types';
|
import type { WorkspaceSettingDetailProps } from './types';
|
||||||
import { WorkspaceQuotaPanel } from './workspace-quota';
|
import { WorkspaceQuotaPanel } from './workspace-quota';
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ export const WorkspaceSettingDetail = ({
|
|||||||
<LabelsPanel />
|
<LabelsPanel />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
|
<TemplateDocSetting />
|
||||||
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
|
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
|
||||||
<EnableCloudPanel onCloseSetting={onCloseSetting} />
|
<EnableCloudPanel onCloseSetting={onCloseSetting} />
|
||||||
<WorkspaceQuotaPanel />
|
<WorkspaceQuotaPanel />
|
||||||
|
|||||||
+4
-4
@@ -5,10 +5,7 @@ export const menuItem = style({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
});
|
});
|
||||||
export const menuItemIcon = style({
|
export const menuItemIcon = style({});
|
||||||
fontSize: 24,
|
|
||||||
lineHeight: 0,
|
|
||||||
});
|
|
||||||
export const menuItemText = style({
|
export const menuItemText = style({
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
width: 0,
|
width: 0,
|
||||||
@@ -17,3 +14,6 @@ export const menuItemText = style({
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
});
|
});
|
||||||
|
export const menuTrigger = style({
|
||||||
|
width: 250,
|
||||||
|
});
|
||||||
+129
@@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
MenuItem,
|
||||||
|
MenuSeparator,
|
||||||
|
MenuTrigger,
|
||||||
|
Switch,
|
||||||
|
} from '@affine/component';
|
||||||
|
import {
|
||||||
|
SettingRow,
|
||||||
|
SettingWrapper,
|
||||||
|
} from '@affine/component/setting-components';
|
||||||
|
import { 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,
|
||||||
|
TemplateListMenu,
|
||||||
|
} from '@affine/core/modules/template-doc';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { DeleteIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './template.css';
|
||||||
|
|
||||||
|
export const TemplateDocSetting = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { featureFlagService, templateDocService } = useServices({
|
||||||
|
FeatureFlagService,
|
||||||
|
TemplateDocService,
|
||||||
|
});
|
||||||
|
const setting = templateDocService.setting;
|
||||||
|
|
||||||
|
const enabled = useLiveData(featureFlagService.flags.enable_template_doc.$);
|
||||||
|
|
||||||
|
const enablePageTemplate = useLiveData(setting.enablePageTemplate$);
|
||||||
|
const pageTemplateDocId = useLiveData(setting.pageTemplateDocId$);
|
||||||
|
const journalTemplateDocId = useLiveData(setting.journalTemplateDocId$);
|
||||||
|
|
||||||
|
const togglePageTemplate = useCallback(
|
||||||
|
(enable: boolean) => {
|
||||||
|
setting.togglePageTemplate(enable);
|
||||||
|
},
|
||||||
|
[setting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatePageTemplate = useCallback(
|
||||||
|
(id?: string) => {
|
||||||
|
setting.updatePageTemplateDocId(id);
|
||||||
|
},
|
||||||
|
[setting]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateJournalTemplate = useCallback(
|
||||||
|
(id?: string) => {
|
||||||
|
setting.updateJournalTemplateDocId(id);
|
||||||
|
},
|
||||||
|
[setting]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!enabled) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingWrapper title={t['com.affine.settings.workspace.template.title']()}>
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.settings.workspace.template.journal']()}
|
||||||
|
desc={t['com.affine.settings.workspace.template.journal-desc']()}
|
||||||
|
>
|
||||||
|
<TemplateSelector
|
||||||
|
current={journalTemplateDocId}
|
||||||
|
onChange={updateJournalTemplate}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.settings.workspace.template.page']()}
|
||||||
|
desc={t['com.affine.settings.workspace.template.page-desc']()}
|
||||||
|
>
|
||||||
|
<Switch checked={enablePageTemplate} onChange={togglePageTemplate} />
|
||||||
|
</SettingRow>
|
||||||
|
{enablePageTemplate ? (
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.settings.workspace.template.journal']()}
|
||||||
|
desc={t['com.affine.settings.workspace.template.journal-desc']()}
|
||||||
|
>
|
||||||
|
<TemplateSelector
|
||||||
|
current={pageTemplateDocId}
|
||||||
|
onChange={updatePageTemplate}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
) : null}
|
||||||
|
</SettingWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TemplateSelectorProps {
|
||||||
|
current?: string;
|
||||||
|
onChange?: (id?: string) => void;
|
||||||
|
}
|
||||||
|
const TemplateSelector = ({ current, onChange }: TemplateSelectorProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const docsService = useService(DocsService);
|
||||||
|
const docDisplayService = useService(DocDisplayMetaService);
|
||||||
|
const doc = useLiveData(current ? docsService.list.doc$(current) : null);
|
||||||
|
const title = useLiveData(doc ? docDisplayService.title$(doc.id) : null);
|
||||||
|
// const isInTrash = useLiveData(doc?.trash$);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TemplateListMenu
|
||||||
|
onSelect={onChange}
|
||||||
|
contentOptions={{ align: 'end' }}
|
||||||
|
suffixItems={
|
||||||
|
<>
|
||||||
|
<MenuSeparator />
|
||||||
|
<MenuItem
|
||||||
|
prefixIcon={<DeleteIcon className={styles.menuItemIcon} />}
|
||||||
|
onClick={() => onChange?.()}
|
||||||
|
type="danger"
|
||||||
|
>
|
||||||
|
{t['com.affine.settings.workspace.template.remove']()}
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuTrigger className={styles.menuTrigger}>
|
||||||
|
{/* TODO: in trash design */}
|
||||||
|
{title ?? t['com.affine.settings.workspace.template.keep-empty']()}
|
||||||
|
</MenuTrigger>
|
||||||
|
</TemplateListMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
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 (
|
|
||||||
<div>
|
|
||||||
Normal Page:
|
|
||||||
<TemplateSelector
|
|
||||||
current={pageTemplateDocId}
|
|
||||||
onChange={updatePageTemplate}
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
Journal:
|
|
||||||
<TemplateSelector
|
|
||||||
current={journalTemplateDocId}
|
|
||||||
onChange={updateJournalTemplate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Menu items={<List onChange={onChange} />}>
|
|
||||||
<Button>{isInTrash ? 'Doc is removed' : (doc?.id ?? 'Unset')}</Button>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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 <DocItem key={doc.id} doc={doc} onClick={handleClick} />;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<MenuItem onClick={handleClick}>
|
|
||||||
<li className={styles.menuItem}>
|
|
||||||
<Icon className={styles.menuItemIcon} />
|
|
||||||
<span className={styles.menuItemText}>{title}</span>
|
|
||||||
</li>
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -10,3 +10,23 @@ export const root = style({
|
|||||||
borderColor: cssVarV2('layer/insideBorder/border'),
|
borderColor: cssVarV2('layer/insideBorder/border'),
|
||||||
background: cssVarV2('button/siderbarPrimary/background'),
|
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,
|
||||||
|
});
|
||||||
|
|||||||
@@ -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 { 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 { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { inferOpenMode } from '@affine/core/utils';
|
import { inferOpenMode } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||||
import { useService } from '@toeverything/infra';
|
import {
|
||||||
|
ArrowDownSmallIcon,
|
||||||
|
EdgelessIcon,
|
||||||
|
PageIcon,
|
||||||
|
PlusIcon,
|
||||||
|
TemplateIcon,
|
||||||
|
} from '@blocksuite/icons/rc';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { type MouseEvent, useCallback } from 'react';
|
import { type MouseEvent, useCallback } from 'react';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
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 {
|
interface AddPageButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sideBottom = { side: 'bottom' as const };
|
const sideBottom = { side: 'bottom' as const };
|
||||||
export function AddPageButton({ className, style }: AddPageButtonProps) {
|
export function AddPageButton(props: AddPageButtonProps) {
|
||||||
const workspaceService = useService(WorkspaceService);
|
const editorSetting = useService(EditorSettingService);
|
||||||
const currentWorkspace = workspaceService.workspace;
|
const newDocDefaultMode = useLiveData(
|
||||||
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
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(
|
const onClickNewPage = useCallback(
|
||||||
(e?: MouseEvent) => {
|
(e?: MouseEvent) => {
|
||||||
pageHelper.createPage(undefined, { at: inferOpenMode(e) });
|
createDoc(e);
|
||||||
track.$.navigationPanel.$.createDoc();
|
track.$.navigationPanel.$.createDoc();
|
||||||
},
|
},
|
||||||
[pageHelper]
|
[createDoc]
|
||||||
);
|
);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const fontStyleOptions = [
|
|||||||
const AffineEditorSettingSchema = z.object({
|
const AffineEditorSettingSchema = z.object({
|
||||||
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
fontFamily: z.enum(['Sans', 'Serif', 'Mono', 'Custom']).default('Sans'),
|
||||||
customFontFamily: z.string().default(''),
|
customFontFamily: z.string().default(''),
|
||||||
newDocDefaultMode: z.enum(['edgeless', 'page']).default('page'),
|
newDocDefaultMode: z.enum(['edgeless', 'page', 'ask']).default('page'),
|
||||||
fullWidthLayout: z.boolean().default(false),
|
fullWidthLayout: z.boolean().default(false),
|
||||||
displayDocInfo: z.boolean().default(true),
|
displayDocInfo: z.boolean().default(true),
|
||||||
displayBiDirectionalLink: z.boolean().default(true),
|
displayBiDirectionalLink: z.boolean().default(true),
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export class EditorSettingService extends Service {
|
|||||||
workspace.docCollection.slots.docCreated.on(docId => {
|
workspace.docCollection.slots.docCreated.on(docId => {
|
||||||
const preferMode = this.editorSetting.settings$.value.newDocDefaultMode;
|
const preferMode = this.editorSetting.settings$.value.newDocDefaultMode;
|
||||||
const docsService = workspace.scope.get(DocsService);
|
const docsService = workspace.scope.get(DocsService);
|
||||||
docsService.list.setPrimaryMode(docId, preferMode);
|
const mode = preferMode === 'ask' ? 'page' : preferMode;
|
||||||
|
docsService.list.setPrimaryMode(docId, mode);
|
||||||
});
|
});
|
||||||
// never dispose, because this service always live longer than workspace
|
// never dispose, because this service always live longer than workspace
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { type Framework } from '@toeverything/infra';
|
|||||||
|
|
||||||
import { DocScope, DocService, DocsService } from '../doc';
|
import { DocScope, DocService, DocsService } from '../doc';
|
||||||
import { EditorSettingService } from '../editor-setting';
|
import { EditorSettingService } from '../editor-setting';
|
||||||
|
import { FeatureFlagService } from '../feature-flag';
|
||||||
|
import { TemplateDocService } from '../template-doc';
|
||||||
import { WorkspaceScope } from '../workspace';
|
import { WorkspaceScope } from '../workspace';
|
||||||
import { JournalService } from './services/journal';
|
import { JournalService } from './services/journal';
|
||||||
import { JournalDocService } from './services/journal-doc';
|
import { JournalDocService } from './services/journal-doc';
|
||||||
@@ -18,7 +20,13 @@ export { suggestJournalDate } from './suggest-journal-date';
|
|||||||
export function configureJournalModule(framework: Framework) {
|
export function configureJournalModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(JournalService, [JournalStore, DocsService, EditorSettingService])
|
.service(JournalService, [
|
||||||
|
JournalStore,
|
||||||
|
DocsService,
|
||||||
|
EditorSettingService,
|
||||||
|
TemplateDocService,
|
||||||
|
FeatureFlagService,
|
||||||
|
])
|
||||||
.store(JournalStore, [DocsService])
|
.store(JournalStore, [DocsService])
|
||||||
.scope(DocScope)
|
.scope(DocScope)
|
||||||
.service(JournalDocService, [DocService, JournalService]);
|
.service(JournalDocService, [DocService, JournalService]);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
} from '../../../blocksuite/initialization';
|
} from '../../../blocksuite/initialization';
|
||||||
import type { DocsService } from '../../doc';
|
import type { DocsService } from '../../doc';
|
||||||
import type { EditorSettingService } from '../../editor-setting';
|
import type { EditorSettingService } from '../../editor-setting';
|
||||||
|
import type { FeatureFlagService } from '../../feature-flag';
|
||||||
|
import type { TemplateDocService } from '../../template-doc';
|
||||||
import type { JournalStore } from '../store/journal';
|
import type { JournalStore } from '../store/journal';
|
||||||
|
|
||||||
export type MaybeDate = Date | string | number;
|
export type MaybeDate = Date | string | number;
|
||||||
@@ -18,7 +20,9 @@ export class JournalService extends Service {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly store: JournalStore,
|
private readonly store: JournalStore,
|
||||||
private readonly docsService: DocsService,
|
private readonly docsService: DocsService,
|
||||||
private readonly editorSettingService: EditorSettingService
|
private readonly editorSettingService: EditorSettingService,
|
||||||
|
private readonly templateDocService: TemplateDocService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -52,8 +56,6 @@ export class JournalService extends Service {
|
|||||||
const day = dayjs(maybeDate);
|
const day = dayjs(maybeDate);
|
||||||
const title = day.format(JOURNAL_DATE_FORMAT);
|
const title = day.format(JOURNAL_DATE_FORMAT);
|
||||||
const docRecord = this.docsService.createDoc();
|
const docRecord = this.docsService.createDoc();
|
||||||
const { doc, release } = this.docsService.open(docRecord.id);
|
|
||||||
this.docsService.list.setPrimaryMode(docRecord.id, 'page');
|
|
||||||
// set created date to match the journal date
|
// set created date to match the journal date
|
||||||
docRecord.setMeta({
|
docRecord.setMeta({
|
||||||
createDate: dayjs()
|
createDate: dayjs()
|
||||||
@@ -63,12 +65,36 @@ export class JournalService extends Service {
|
|||||||
.toDate()
|
.toDate()
|
||||||
.getTime(),
|
.getTime(),
|
||||||
});
|
});
|
||||||
const docProps: DocProps = {
|
|
||||||
page: { title: new Text(title) },
|
const enableTemplateDoc =
|
||||||
note: this.editorSettingService.editorSetting.get('affine:note'),
|
this.featureFlagService.flags.enable_template_doc.value;
|
||||||
};
|
const enablePageTemplate =
|
||||||
initDocFromProps(doc.blockSuiteDoc, docProps);
|
this.templateDocService.setting.enablePageTemplate$.value;
|
||||||
release();
|
const pageTemplateDocId =
|
||||||
|
this.templateDocService.setting.pageTemplateDocId$.value;
|
||||||
|
const journalTemplateDocId =
|
||||||
|
this.templateDocService.setting.journalTemplateDocId$.value;
|
||||||
|
// if journal template configured
|
||||||
|
if (enableTemplateDoc && journalTemplateDocId) {
|
||||||
|
this.docsService
|
||||||
|
.duplicateFromTemplate(journalTemplateDocId, docRecord.id)
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
// journal template not configured, use page template
|
||||||
|
else if (enableTemplateDoc && enablePageTemplate && pageTemplateDocId) {
|
||||||
|
this.docsService
|
||||||
|
.duplicateFromTemplate(pageTemplateDocId, docRecord.id)
|
||||||
|
.catch(console.error);
|
||||||
|
} else {
|
||||||
|
const { doc, release } = this.docsService.open(docRecord.id);
|
||||||
|
this.docsService.list.setPrimaryMode(docRecord.id, 'page');
|
||||||
|
const docProps: DocProps = {
|
||||||
|
page: { title: new Text(title) },
|
||||||
|
note: this.editorSettingService.editorSetting.get('affine:note'),
|
||||||
|
};
|
||||||
|
initDocFromProps(doc.blockSuiteDoc, docProps);
|
||||||
|
release();
|
||||||
|
}
|
||||||
this.setJournalDate(docRecord.id, title);
|
this.setJournalDate(docRecord.id, title);
|
||||||
return docRecord;
|
return docRecord;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,14 @@ export class TemplateDocSetting extends Entity {
|
|||||||
|
|
||||||
loading$ = this.store.watchIsLoading();
|
loading$ = this.store.watchIsLoading();
|
||||||
setting$ = this.store.watchSetting();
|
setting$ = this.store.watchSetting();
|
||||||
|
enablePageTemplate$ = this.store.watchSettingKey('enablePageTemplate');
|
||||||
pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId');
|
pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId');
|
||||||
journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId');
|
journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId');
|
||||||
|
|
||||||
|
togglePageTemplate(enable: boolean) {
|
||||||
|
this.store.updateSetting('enablePageTemplate', enable);
|
||||||
|
}
|
||||||
|
|
||||||
updatePageTemplateDocId(id?: string) {
|
updatePageTemplateDocId(id?: string) {
|
||||||
this.store.updateSetting('pageTemplateId', id);
|
this.store.updateSetting('pageTemplateId', id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export class TemplateDocSettingStore extends Store {
|
|||||||
) {
|
) {
|
||||||
const db = this.dbService.userdataDB$.value;
|
const db = this.dbService.userdataDB$.value;
|
||||||
const prev = db.settings.find({ key: this.key })[0]?.value ?? {};
|
const prev = db.settings.find({ key: this.key })[0]?.value ?? {};
|
||||||
db.settings.update(this.key, {
|
db.settings.create({
|
||||||
|
key: this.key,
|
||||||
value: { ...prev, [key]: value },
|
value: { ...prev, [key]: value },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type TemplateDocSettings = {
|
export type TemplateDocSettings = {
|
||||||
|
enablePageTemplate?: boolean;
|
||||||
pageTemplateId?: string;
|
pageTemplateId?: string;
|
||||||
journalTemplateId?: string;
|
journalTemplateId?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,39 +1,13 @@
|
|||||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const list = style({
|
export const list = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 4,
|
gap: 4,
|
||||||
minWidth: 250,
|
|
||||||
maxWidth: 355,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const item = style({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
padding: 4,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const itemIcon = style({
|
|
||||||
fontSize: 20,
|
|
||||||
lineHeight: 0,
|
|
||||||
color: cssVarV2.icon.primary,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const itemText = style({
|
|
||||||
width: 0,
|
|
||||||
flex: 1,
|
|
||||||
fontSize: 14,
|
|
||||||
lineHeight: '22px',
|
|
||||||
color: cssVarV2.text.primary,
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const menuContent = style({
|
export const menuContent = style({
|
||||||
|
width: 280,
|
||||||
paddingRight: 0,
|
paddingRight: 0,
|
||||||
});
|
});
|
||||||
export const scrollableViewport = style({
|
export const scrollableViewport = style({
|
||||||
|
|||||||
@@ -1,41 +1,45 @@
|
|||||||
import { Menu, MenuItem, type MenuProps, Scrollable } from '@affine/component';
|
import { Menu, MenuItem, type MenuProps, Scrollable } from '@affine/component';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type PropsWithChildren, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { type DocRecord, DocsService } from '../../doc';
|
import { type DocRecord } from '../../doc';
|
||||||
import { DocDisplayMetaService } from '../../doc-display-meta';
|
import { DocDisplayMetaService } from '../../doc-display-meta';
|
||||||
import { TemplateDocService } from '../services/template-doc';
|
import { TemplateDocService } from '../services/template-doc';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
interface CommonProps {
|
interface CommonProps {
|
||||||
target?: string;
|
onSelect?: (docId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocItemProps extends CommonProps {
|
interface DocItemProps extends CommonProps {
|
||||||
doc: DocRecord;
|
doc: DocRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocItem = ({ doc, target }: DocItemProps) => {
|
const DocItem = ({ doc, onSelect }: DocItemProps) => {
|
||||||
const docDisplayService = useService(DocDisplayMetaService);
|
const docDisplayService = useService(DocDisplayMetaService);
|
||||||
const Icon = useLiveData(docDisplayService.icon$(doc.id));
|
const Icon = useLiveData(docDisplayService.icon$(doc.id));
|
||||||
const title = useLiveData(docDisplayService.title$(doc.id));
|
const title = useLiveData(docDisplayService.title$(doc.id));
|
||||||
const docsService = useService(DocsService);
|
|
||||||
|
|
||||||
const onClick = useAsyncCallback(async () => {
|
const onClick = useAsyncCallback(async () => {
|
||||||
await docsService.duplicateFromTemplate(doc.id, target);
|
onSelect?.(doc.id);
|
||||||
}, [doc.id, docsService, target]);
|
}, [doc.id, onSelect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={onClick} style={{ padding: 0 }}>
|
<MenuItem prefixIcon={<Icon />} onClick={onClick}>
|
||||||
<li className={styles.item}>
|
{title}
|
||||||
<Icon className={styles.itemIcon} />
|
|
||||||
<span className={styles.itemText}>{title}</span>
|
|
||||||
</li>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TemplateListMenuContent = ({ target }: CommonProps) => {
|
interface TemplateListMenuContentProps extends CommonProps {
|
||||||
|
prefixItems?: React.ReactNode;
|
||||||
|
suffixItems?: React.ReactNode;
|
||||||
|
}
|
||||||
|
export const TemplateListMenuContent = ({
|
||||||
|
prefixItems,
|
||||||
|
suffixItems,
|
||||||
|
...props
|
||||||
|
}: TemplateListMenuContentProps) => {
|
||||||
const templateDocService = useService(TemplateDocService);
|
const templateDocService = useService(TemplateDocService);
|
||||||
const [templateDocs] = useState(() =>
|
const [templateDocs] = useState(() =>
|
||||||
templateDocService.list.getTemplateDocs()
|
templateDocService.list.getTemplateDocs()
|
||||||
@@ -43,33 +47,48 @@ export const TemplateListMenuContent = ({ target }: CommonProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
|
{prefixItems}
|
||||||
{templateDocs.map(doc => (
|
{templateDocs.map(doc => (
|
||||||
<DocItem key={doc.id} doc={doc} target={target} />
|
<DocItem key={doc.id} doc={doc} {...props} />
|
||||||
))}
|
))}
|
||||||
|
{suffixItems}
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TemplateListMenuContentScrollable = ({ target }: CommonProps) => {
|
export const TemplateListMenuContentScrollable = (
|
||||||
|
props: TemplateListMenuContentProps
|
||||||
|
) => {
|
||||||
return (
|
return (
|
||||||
<Scrollable.Root>
|
<Scrollable.Root>
|
||||||
<Scrollable.Scrollbar />
|
<Scrollable.Scrollbar />
|
||||||
<Scrollable.Viewport className={styles.scrollableViewport}>
|
<Scrollable.Viewport className={styles.scrollableViewport}>
|
||||||
<TemplateListMenuContent target={target} />
|
<TemplateListMenuContent {...props} />
|
||||||
</Scrollable.Viewport>
|
</Scrollable.Viewport>
|
||||||
</Scrollable.Root>
|
</Scrollable.Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface TemplateListMenuProps
|
||||||
|
extends TemplateListMenuContentProps,
|
||||||
|
Omit<MenuProps, 'items'> {}
|
||||||
export const TemplateListMenu = ({
|
export const TemplateListMenu = ({
|
||||||
children,
|
children,
|
||||||
target,
|
onSelect,
|
||||||
|
prefixItems,
|
||||||
|
suffixItems,
|
||||||
contentOptions,
|
contentOptions,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: PropsWithChildren<CommonProps> & Omit<MenuProps, 'items'>) => {
|
}: TemplateListMenuProps) => {
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
items={<TemplateListMenuContentScrollable target={target} />}
|
items={
|
||||||
|
<TemplateListMenuContentScrollable
|
||||||
|
onSelect={onSelect}
|
||||||
|
prefixItems={prefixItems}
|
||||||
|
suffixItems={suffixItems}
|
||||||
|
/>
|
||||||
|
}
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
...contentOptions,
|
...contentOptions,
|
||||||
className: styles.menuContent,
|
className: styles.menuContent,
|
||||||
|
|||||||
@@ -1628,5 +1628,14 @@
|
|||||||
"com.affine.page-starter-bar.start": "Start",
|
"com.affine.page-starter-bar.start": "Start",
|
||||||
"com.affine.page-starter-bar.template": "Template",
|
"com.affine.page-starter-bar.template": "Template",
|
||||||
"com.affine.page-starter-bar.ai": "With AI",
|
"com.affine.page-starter-bar.ai": "With AI",
|
||||||
"com.affine.page-starter-bar.edgeless": "Edgeless"
|
"com.affine.page-starter-bar.edgeless": "Edgeless",
|
||||||
|
"Template": "Template",
|
||||||
|
"com.affine.settings.workspace.template.title": "My Templates",
|
||||||
|
"com.affine.settings.workspace.template.journal": "Template for journal",
|
||||||
|
"com.affine.settings.workspace.template.journal-desc": "Select a template for your journal",
|
||||||
|
"com.affine.settings.workspace.template.keep-empty": "Keep empty",
|
||||||
|
"com.affine.settings.workspace.template.page": "New doc with template",
|
||||||
|
"com.affine.settings.workspace.template.page-desc": "New docs will use the specified template, ignoring default settings.",
|
||||||
|
"com.affine.settings.workspace.template.page-select": "Template for new doc",
|
||||||
|
"com.affine.settings.workspace.template.remove": "Remove template"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user