feat(core): new template doc property (#9538)
close AF-2045, AF-2047, AF-2065
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import type { DocRecord, DocsService } from '../../doc';
|
||||
import type { TemplateDocListStore } from '../store/list';
|
||||
|
||||
export class TemplateDocList extends Entity {
|
||||
constructor(
|
||||
public listStore: TemplateDocListStore,
|
||||
public docsService: DocsService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public isTemplate$(docId: string) {
|
||||
return LiveData.from(this.listStore.watchTemplateDoc(docId), false);
|
||||
}
|
||||
|
||||
public getTemplateDocs() {
|
||||
return this.listStore
|
||||
.getTemplateDocIds()
|
||||
.map(id => this.docsService.list.doc$(id).value)
|
||||
.filter((doc): doc is DocRecord => !!doc && !doc.trash$.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Entity } from '@toeverything/infra';
|
||||
|
||||
export type TemplateDocSettings = {
|
||||
templateId?: string;
|
||||
journalTemplateId?: string;
|
||||
};
|
||||
|
||||
export class TemplateDocSetting extends Entity {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
21
packages/frontend/core/src/modules/template-doc/index.ts
Normal file
21
packages/frontend/core/src/modules/template-doc/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceDBService } from '../db';
|
||||
import { DocsService } from '../doc';
|
||||
import { WorkspaceScope } from '../workspace';
|
||||
import { TemplateDocList } from './entities/list';
|
||||
import { TemplateDocSetting } from './entities/setting';
|
||||
import { TemplateDocService } from './services/template-doc';
|
||||
import { TemplateDocListStore } from './store/list';
|
||||
|
||||
export { TemplateDocService };
|
||||
export * from './view/template-list-menu';
|
||||
|
||||
export const configureTemplateDocModule = (framework: Framework) => {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(TemplateDocService)
|
||||
.store(TemplateDocListStore, [WorkspaceDBService])
|
||||
.entity(TemplateDocList, [TemplateDocListStore, DocsService])
|
||||
.entity(TemplateDocSetting);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { TemplateDocList } from '../entities/list';
|
||||
import { TemplateDocSetting } from '../entities/setting';
|
||||
|
||||
export class TemplateDocService extends Service {
|
||||
public readonly list = this.framework.createEntity(TemplateDocList);
|
||||
public readonly setting = this.framework.createEntity(TemplateDocSetting);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Store } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { WorkspaceDBService } from '../../db';
|
||||
|
||||
export class TemplateDocListStore extends Store {
|
||||
constructor(private readonly dbService: WorkspaceDBService) {
|
||||
super();
|
||||
}
|
||||
|
||||
isTemplateDoc(docId: string) {
|
||||
return !!this.dbService.db.docProperties.find({
|
||||
id: docId,
|
||||
isTemplate: true,
|
||||
})[0]?.isTemplate;
|
||||
}
|
||||
|
||||
watchTemplateDoc(docId: string) {
|
||||
return this.dbService.db.docProperties
|
||||
.find$({ id: docId, isTemplate: true })
|
||||
.pipe(map(res => res[0]?.isTemplate));
|
||||
}
|
||||
|
||||
getTemplateDocIds() {
|
||||
return this.dbService.db.docProperties
|
||||
.find({ isTemplate: true })
|
||||
.map(property => property.id);
|
||||
}
|
||||
|
||||
watchTemplateDocs() {
|
||||
return this.dbService.db.docProperties.find$({ isTemplate: true });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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({
|
||||
paddingRight: 0,
|
||||
});
|
||||
export const scrollableViewport = style({
|
||||
paddingRight: 8,
|
||||
maxHeight: 360,
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
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 { type DocRecord, DocsService } from '../../doc';
|
||||
import { DocDisplayMetaService } from '../../doc-display-meta';
|
||||
import { TemplateDocService } from '../services/template-doc';
|
||||
import * as styles from './styles.css';
|
||||
interface CommonProps {
|
||||
target?: string;
|
||||
}
|
||||
|
||||
interface DocItemProps extends CommonProps {
|
||||
doc: DocRecord;
|
||||
}
|
||||
|
||||
const DocItem = ({ doc, target }: 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]);
|
||||
|
||||
return (
|
||||
<MenuItem onClick={onClick} style={{ padding: 0 }}>
|
||||
<li className={styles.item}>
|
||||
<Icon className={styles.itemIcon} />
|
||||
<span className={styles.itemText}>{title}</span>
|
||||
</li>
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const TemplateListMenuContent = ({ target }: CommonProps) => {
|
||||
const templateDocService = useService(TemplateDocService);
|
||||
const [templateDocs] = useState(() =>
|
||||
templateDocService.list.getTemplateDocs()
|
||||
);
|
||||
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{templateDocs.map(doc => (
|
||||
<DocItem key={doc.id} doc={doc} target={target} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export const TemplateListMenuContentScrollable = ({ target }: CommonProps) => {
|
||||
return (
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Scrollbar />
|
||||
<Scrollable.Viewport className={styles.scrollableViewport}>
|
||||
<TemplateListMenuContent target={target} />
|
||||
</Scrollable.Viewport>
|
||||
</Scrollable.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const TemplateListMenu = ({
|
||||
children,
|
||||
target,
|
||||
contentOptions,
|
||||
...otherProps
|
||||
}: PropsWithChildren<CommonProps> & Omit<MenuProps, 'items'>) => {
|
||||
return (
|
||||
<Menu
|
||||
items={<TemplateListMenuContentScrollable target={target} />}
|
||||
contentOptions={{
|
||||
...contentOptions,
|
||||
className: styles.menuContent,
|
||||
}}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user