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

@@ -9,9 +9,14 @@ export class TemplateDocSetting extends Entity {
loading$ = this.store.watchIsLoading();
setting$ = this.store.watchSetting();
enablePageTemplate$ = this.store.watchSettingKey('enablePageTemplate');
pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId');
journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId');
togglePageTemplate(enable: boolean) {
this.store.updateSetting('enablePageTemplate', enable);
}
updatePageTemplateDocId(id?: string) {
this.store.updateSetting('pageTemplateId', id);
}

View File

@@ -40,7 +40,8 @@ export class TemplateDocSettingStore extends Store {
) {
const db = this.dbService.userdataDB$.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 },
});
}

View File

@@ -1,4 +1,5 @@
export type TemplateDocSettings = {
enablePageTemplate?: boolean;
pageTemplateId?: string;
journalTemplateId?: string;
};

View File

@@ -1,39 +1,13 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const list = style({
display: 'flex',
flexDirection: 'column',
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({
width: 280,
paddingRight: 0,
});
export const scrollableViewport = style({

View File

@@ -1,41 +1,45 @@
import { Menu, MenuItem, type MenuProps, Scrollable } from '@affine/component';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
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 { TemplateDocService } from '../services/template-doc';
import * as styles from './styles.css';
interface CommonProps {
target?: string;
onSelect?: (docId: string) => void;
}
interface DocItemProps extends CommonProps {
doc: DocRecord;
}
const DocItem = ({ doc, target }: DocItemProps) => {
const DocItem = ({ doc, onSelect }: DocItemProps) => {
const docDisplayService = useService(DocDisplayMetaService);
const Icon = useLiveData(docDisplayService.icon$(doc.id));
const title = useLiveData(docDisplayService.title$(doc.id));
const docsService = useService(DocsService);
const onClick = useAsyncCallback(async () => {
await docsService.duplicateFromTemplate(doc.id, target);
}, [doc.id, docsService, target]);
onSelect?.(doc.id);
}, [doc.id, onSelect]);
return (
<MenuItem onClick={onClick} style={{ padding: 0 }}>
<li className={styles.item}>
<Icon className={styles.itemIcon} />
<span className={styles.itemText}>{title}</span>
</li>
<MenuItem prefixIcon={<Icon />} onClick={onClick}>
{title}
</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 [templateDocs] = useState(() =>
templateDocService.list.getTemplateDocs()
@@ -43,33 +47,48 @@ export const TemplateListMenuContent = ({ target }: CommonProps) => {
return (
<ul className={styles.list}>
{prefixItems}
{templateDocs.map(doc => (
<DocItem key={doc.id} doc={doc} target={target} />
<DocItem key={doc.id} doc={doc} {...props} />
))}
{suffixItems}
</ul>
);
};
export const TemplateListMenuContentScrollable = ({ target }: CommonProps) => {
export const TemplateListMenuContentScrollable = (
props: TemplateListMenuContentProps
) => {
return (
<Scrollable.Root>
<Scrollable.Scrollbar />
<Scrollable.Viewport className={styles.scrollableViewport}>
<TemplateListMenuContent target={target} />
<TemplateListMenuContent {...props} />
</Scrollable.Viewport>
</Scrollable.Root>
);
};
interface TemplateListMenuProps
extends TemplateListMenuContentProps,
Omit<MenuProps, 'items'> {}
export const TemplateListMenu = ({
children,
target,
onSelect,
prefixItems,
suffixItems,
contentOptions,
...otherProps
}: PropsWithChildren<CommonProps> & Omit<MenuProps, 'items'>) => {
}: TemplateListMenuProps) => {
return (
<Menu
items={<TemplateListMenuContentScrollable target={target} />}
items={
<TemplateListMenuContentScrollable
onSelect={onSelect}
prefixItems={prefixItems}
suffixItems={suffixItems}
/>
}
contentOptions={{
...contentOptions,
className: styles.menuContent,