feat(core): new template doc property (#9538)
close AF-2045, AF-2047, AF-2065
This commit is contained in:
@@ -68,6 +68,7 @@ import {
|
||||
} from './specs/custom/spec-patchers';
|
||||
import { createEdgelessModeSpecs } from './specs/edgeless';
|
||||
import { createPageModeSpecs } from './specs/page';
|
||||
import { StarterBar } from './starter-bar';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
const adapted = {
|
||||
@@ -334,6 +335,7 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
data-testid="page-editor-blank"
|
||||
onClick={onClickBlank}
|
||||
></div>
|
||||
<StarterBar doc={page} />
|
||||
{!shared && displayBiDirectionalLink ? (
|
||||
<BiDirectionalLinkPanel />
|
||||
) : null}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
import { container } from './bi-directional-link-panel.css';
|
||||
|
||||
export const root = style([
|
||||
container,
|
||||
{
|
||||
paddingBottom: 6,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
color: cssVarV2.text.primary,
|
||||
},
|
||||
]);
|
||||
|
||||
export const badges = style({
|
||||
display: 'flex',
|
||||
gap: 12,
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const badge = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
padding: '2px 8px',
|
||||
borderRadius: 40,
|
||||
backgroundColor: cssVarV2.layer.background.secondary,
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
position: 'relative',
|
||||
|
||||
':before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,.04)',
|
||||
borderRadius: 'inherit',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.2s ease',
|
||||
},
|
||||
|
||||
selectors: {
|
||||
'&:hover:before': {
|
||||
opacity: 1,
|
||||
},
|
||||
'&[data-active="true"]:before': {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const badgeIcon = style({
|
||||
fontSize: 16,
|
||||
lineHeight: 0,
|
||||
});
|
||||
|
||||
export const badgeText = style({});
|
||||
@@ -0,0 +1,126 @@
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import {
|
||||
TemplateDocService,
|
||||
TemplateListMenu,
|
||||
} from '@affine/core/modules/template-doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { Store } from '@blocksuite/affine/store';
|
||||
import {
|
||||
AiIcon,
|
||||
EdgelessIcon,
|
||||
TemplateColoredIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import * as styles from './starter-bar.css';
|
||||
|
||||
const Badge = forwardRef<
|
||||
HTMLLIElement,
|
||||
HTMLAttributes<HTMLLIElement> & {
|
||||
icon: React.ReactNode;
|
||||
text: string;
|
||||
active?: boolean;
|
||||
}
|
||||
>(function Badge({ icon, text, className, active, ...attrs }, ref) {
|
||||
return (
|
||||
<li
|
||||
data-active={active}
|
||||
className={clsx(styles.badge, className)}
|
||||
ref={ref}
|
||||
{...attrs}
|
||||
>
|
||||
<span className={styles.badgeText}>{text}</span>
|
||||
<span className={styles.badgeIcon}>{icon}</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
const StarterBarNotEmpty = ({ doc }: { doc: Store }) => {
|
||||
const t = useI18n();
|
||||
|
||||
const templateDocService = useService(TemplateDocService);
|
||||
const featureFlagService = useService(FeatureFlagService);
|
||||
|
||||
const [templateMenuOpen, setTemplateMenuOpen] = useState(false);
|
||||
|
||||
const isTemplate = useLiveData(
|
||||
useMemo(
|
||||
() => templateDocService.list.isTemplate$(doc.id),
|
||||
[doc.id, templateDocService.list]
|
||||
)
|
||||
);
|
||||
const enableTemplateDoc = useLiveData(
|
||||
featureFlagService.flags.enable_template_doc.$
|
||||
);
|
||||
|
||||
const showAI = false;
|
||||
const showEdgeless = false;
|
||||
const showTemplate = !isTemplate && enableTemplateDoc;
|
||||
|
||||
if (!showAI && !showEdgeless && !showTemplate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
{t['com.affine.page-starter-bar.start']()}
|
||||
<ul className={styles.badges}>
|
||||
{showAI ? (
|
||||
<Badge
|
||||
icon={<AiIcon />}
|
||||
text={t['com.affine.page-starter-bar.ai']()}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showTemplate ? (
|
||||
<TemplateListMenu
|
||||
target={doc.id}
|
||||
rootOptions={{
|
||||
open: templateMenuOpen,
|
||||
onOpenChange: setTemplateMenuOpen,
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
data-testid="template-docs-badge"
|
||||
icon={<TemplateColoredIcon />}
|
||||
text={t['com.affine.page-starter-bar.template']()}
|
||||
active={templateMenuOpen}
|
||||
/>
|
||||
</TemplateListMenu>
|
||||
) : null}
|
||||
|
||||
{showEdgeless ? (
|
||||
<Badge
|
||||
icon={<EdgelessIcon />}
|
||||
text={t['com.affine.page-starter-bar.edgeless']()}
|
||||
/>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const StarterBar = ({ doc }: { doc: Store }) => {
|
||||
const [isEmpty, setIsEmpty] = useState(doc.isEmpty);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = doc.slots.blockUpdated.on(() => {
|
||||
setIsEmpty(doc.isEmpty);
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [doc]);
|
||||
|
||||
if (!isEmpty) return null;
|
||||
|
||||
return <StarterBarNotEmpty doc={doc} />;
|
||||
};
|
||||
Reference in New Issue
Block a user