feat(core): add track events for editor header (#7661)

close AF-1054
This commit is contained in:
JimmFly
2024-08-07 05:52:40 +00:00
parent b5e543c406
commit 74025fc85e
12 changed files with 125 additions and 19 deletions

View File

@@ -1,6 +1,8 @@
import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud'; import { useEnableCloud } from '@affine/core/hooks/affine/use-enable-cloud';
import { track } from '@affine/core/mixpanel';
import type { Doc } from '@blocksuite/store'; import type { Doc } from '@blocksuite/store';
import type { Workspace } from '@toeverything/infra'; import { type Workspace } from '@toeverything/infra';
import { useCallback } from 'react';
import { ShareMenu } from './share-menu'; import { ShareMenu } from './share-menu';
@@ -11,6 +13,11 @@ type SharePageModalProps = {
export const SharePageButton = ({ workspace, page }: SharePageModalProps) => { export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
const confirmEnableCloud = useEnableCloud(); const confirmEnableCloud = useEnableCloud();
const handleOpenShareModal = useCallback((open: boolean) => {
if (open) {
track.$.sharePanel.$.open();
}
}, []);
return ( return (
<ShareMenu <ShareMenu
@@ -21,6 +28,7 @@ export const SharePageButton = ({ workspace, page }: SharePageModalProps) => {
openPageId: page.id, openPageId: page.id,
}) })
} }
onOpenShareModal={handleOpenShareModal}
/> />
); );
}; };

View File

@@ -21,6 +21,7 @@ export interface ShareMenuProps extends PropsWithChildren {
workspaceMetadata: WorkspaceMetadata; workspaceMetadata: WorkspaceMetadata;
currentPage: Doc; currentPage: Doc;
onEnableAffineCloud: () => void; onEnableAffineCloud: () => void;
onOpenShareModal?: (open: boolean) => void;
} }
export const ShareMenuContent = (props: ShareMenuProps) => { export const ShareMenuContent = (props: ShareMenuProps) => {
@@ -73,6 +74,7 @@ const LocalShareMenu = (props: ShareMenuProps) => {
}} }}
rootOptions={{ rootOptions={{
modal: false, modal: false,
onOpenChange: props.onOpenShareModal,
}} }}
> >
<div data-testid="local-share-menu-button"> <div data-testid="local-share-menu-button">
@@ -92,6 +94,7 @@ const CloudShareMenu = (props: ShareMenuProps) => {
}} }}
rootOptions={{ rootOptions={{
modal: false, modal: false,
onOpenChange: props.onOpenShareModal,
}} }}
> >
<div data-testid="cloud-share-menu-button"> <div data-testid="cloud-share-menu-button">

View File

@@ -106,7 +106,7 @@ export const AffineSharePage = (props: ShareMenuProps) => {
await shareService.share.enableShare( await shareService.share.enableShare(
mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page
); );
track.$.header.share.createShareLink({ track.$.sharePanel.$.createShareLink({
mode, mode,
}); });
notify.success({ notify.success({

View File

@@ -1,4 +1,5 @@
import { FavoriteTag } from '@affine/core/components/page-list'; import { FavoriteTag } from '@affine/core/components/page-list';
import { track } from '@affine/core/mixpanel';
import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties'; import { CompatibleFavoriteItemsAdapter } from '@affine/core/modules/properties';
import { toast } from '@affine/core/utils'; import { toast } from '@affine/core/utils';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -30,11 +31,16 @@ export const useFavorite = (pageId: string) => {
export const FavoriteButton = ({ pageId }: FavoriteButtonProps) => { export const FavoriteButton = ({ pageId }: FavoriteButtonProps) => {
const { favorite, toggleFavorite } = useFavorite(pageId); const { favorite, toggleFavorite } = useFavorite(pageId);
const handleFavorite = useCallback(() => {
track.$.header.actions.toggleFavorite();
toggleFavorite();
}, [toggleFavorite]);
return ( return (
<FavoriteTag <FavoriteTag
data-testid="pin-button" data-testid="pin-button"
active={!!favorite} active={!!favorite}
onClick={toggleFavorite} onClick={handleFavorite}
/> />
); );
}; };

View File

@@ -1,15 +1,20 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { openInfoModalAtom } from '@affine/core/atoms'; import { openInfoModalAtom } from '@affine/core/atoms';
import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { InformationIcon } from '@blocksuite/icons/rc'; import { InformationIcon } from '@blocksuite/icons/rc';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
export const InfoButton = () => { export const InfoButton = () => {
const setOpenInfoModal = useSetAtom(openInfoModalAtom); const setOpenInfoModal = useSetAtom(openInfoModalAtom);
const t = useI18n(); const t = useI18n();
const onOpenInfoModal = () => {
const onOpenInfoModal = useCallback(() => {
track.$.header.actions.openDocInfo();
setOpenInfoModal(true); setOpenInfoModal(true);
}; }, [setOpenInfoModal]);
return ( return (
<IconButton <IconButton
size="20" size="20"

View File

@@ -109,6 +109,7 @@ export const PageHeaderMenuButton = ({
const setOpenHistoryTipsModal = useSetAtom(openHistoryTipsModalAtom); const setOpenHistoryTipsModal = useSetAtom(openHistoryTipsModalAtom);
const openHistoryModal = useCallback(() => { const openHistoryModal = useCallback(() => {
track.$.header.history.open();
if (workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) { if (workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
return setHistoryModalOpen(true); return setHistoryModalOpen(true);
} }
@@ -116,9 +117,10 @@ export const PageHeaderMenuButton = ({
}, [setOpenHistoryTipsModal, workspace.flavour]); }, [setOpenHistoryTipsModal, workspace.flavour]);
const setOpenInfoModal = useSetAtom(openInfoModalAtom); const setOpenInfoModal = useSetAtom(openInfoModalAtom);
const openInfoModal = () => { const openInfoModal = useCallback(() => {
track.$.header.pageInfo.open();
setOpenInfoModal(true); setOpenInfoModal(true);
}; }, [setOpenInfoModal]);
const handleOpenInNewTab = useCallback(() => { const handleOpenInNewTab = useCallback(() => {
workbench.openDoc(pageId, { workbench.openDoc(pageId, {
@@ -133,6 +135,7 @@ export const PageHeaderMenuButton = ({
}, [pageId, workbench]); }, [pageId, workbench]);
const handleOpenTrashModal = useCallback(() => { const handleOpenTrashModal = useCallback(() => {
track.$.header.docOptions.deleteDoc();
setTrashModal({ setTrashModal({
open: true, open: true,
pageIds: [pageId], pageIds: [pageId],
@@ -140,8 +143,14 @@ export const PageHeaderMenuButton = ({
}); });
}, [doc.meta$.value.title, pageId, setTrashModal]); }, [doc.meta$.value.title, pageId, setTrashModal]);
const handleRename = useCallback(() => {
rename?.();
track.$.header.docOptions.renameDoc();
}, [rename]);
const handleSwitchMode = useCallback(() => { const handleSwitchMode = useCallback(() => {
doc.toggleMode(); doc.toggleMode();
track.$.header.docOptions.switchPageMode();
toast( toast(
currentMode === 'page' currentMode === 'page'
? t['com.affine.toastMessage.edgelessMode']() ? t['com.affine.toastMessage.edgelessMode']()
@@ -153,17 +162,24 @@ export const PageHeaderMenuButton = ({
transition: 'all 0.3s', transition: 'all 0.3s',
}; };
const handleMenuOpenChange = useCallback((open: boolean) => {
if (open) {
track.$.header.docOptions.open();
}
}, []);
const exportHandler = useExportPage(doc.blockSuiteDoc); const exportHandler = useExportPage(doc.blockSuiteDoc);
const handleDuplicate = useCallback(() => { const handleDuplicate = useCallback(() => {
duplicate(pageId); duplicate(pageId);
track.$.header.actions.createDoc({ track.$.header.docOptions.createDoc({
control: 'duplicate', control: 'duplicate',
}); });
}, [duplicate, pageId]); }, [duplicate, pageId]);
const onImportFile = useAsyncCallback(async () => { const onImportFile = useAsyncCallback(async () => {
const options = await importFile(); const options = await importFile();
track.$.header.docOptions.import();
if (options.isWorkspaceFile) { if (options.isWorkspaceFile) {
track.$.header.actions.createWorkspace({ track.$.header.actions.createWorkspace({
control: 'import', control: 'import',
@@ -175,6 +191,17 @@ export const PageHeaderMenuButton = ({
} }
}, [importFile]); }, [importFile]);
const handleShareMenuOpenChange = useCallback((open: boolean) => {
if (open) {
track.$.sharePanel.$.open();
}
}, []);
const handleToggleFavorite = useCallback(() => {
track.$.header.docOptions.toggleFavorite();
toggleFavorite();
}, [toggleFavorite]);
const showResponsiveMenu = hideShare; const showResponsiveMenu = hideShare;
const ResponsiveMenuItems = ( const ResponsiveMenuItems = (
<> <>
@@ -204,6 +231,9 @@ export const PageHeaderMenuButton = ({
</MenuIcon> </MenuIcon>
), ),
}} }}
subOptions={{
onOpenChange: handleShareMenuOpenChange,
}}
> >
{t['com.affine.share-menu.shareButton']()} {t['com.affine.share-menu.shareButton']()}
</MenuSub> </MenuSub>
@@ -223,7 +253,7 @@ export const PageHeaderMenuButton = ({
</MenuIcon> </MenuIcon>
} }
data-testid="editor-option-menu-rename" data-testid="editor-option-menu-rename"
onSelect={rename} onSelect={handleRename}
style={menuItemStyle} style={menuItemStyle}
> >
{t['Rename']()} {t['Rename']()}
@@ -246,7 +276,7 @@ export const PageHeaderMenuButton = ({
</MenuItem> </MenuItem>
<MenuItem <MenuItem
data-testid="editor-option-menu-favorite" data-testid="editor-option-menu-favorite"
onSelect={toggleFavorite} onSelect={handleToggleFavorite}
style={menuItemStyle} style={menuItemStyle}
preFix={ preFix={
<MenuIcon> <MenuIcon>
@@ -389,6 +419,9 @@ export const PageHeaderMenuButton = ({
contentOptions={{ contentOptions={{
align: 'center', align: 'center',
}} }}
rootOptions={{
onOpenChange: handleMenuOpenChange,
}}
> >
<HeaderDropDownButton /> <HeaderDropDownButton />
</Menu> </Menu>

View File

@@ -18,13 +18,14 @@ export interface BlockSuiteHeaderTitleProps {
isPublic?: boolean; isPublic?: boolean;
inputHandleRef?: InlineEditProps['handleRef']; inputHandleRef?: InlineEditProps['handleRef'];
className?: string; className?: string;
onEditSave?: () => void;
} }
const inputAttrs = { const inputAttrs = {
'data-testid': 'title-content', 'data-testid': 'title-content',
} as HTMLAttributes<HTMLInputElement>; } as HTMLAttributes<HTMLInputElement>;
export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => { export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
const { docCollection, pageId, isPublic, inputHandleRef } = props; const { docCollection, pageId, isPublic, inputHandleRef, onEditSave } = props;
const currentPage = docCollection.getDoc(pageId); const currentPage = docCollection.getDoc(pageId);
const pageMeta = useBlockSuiteDocMeta(docCollection).find( const pageMeta = useBlockSuiteDocMeta(docCollection).find(
meta => meta.id === currentPage?.id meta => meta.id === currentPage?.id
@@ -34,9 +35,10 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
const onChange = useCallback( const onChange = useCallback(
(v: string) => { (v: string) => {
onEditSave?.();
setDocTitle(currentPage?.id || '', v); setDocTitle(currentPage?.id || '', v);
}, },
[currentPage?.id, setDocTitle] [currentPage?.id, onEditSave, setDocTitle]
); );
return ( return (

View File

@@ -1,4 +1,5 @@
import { MenuIcon, MenuItem, MenuSub } from '@affine/component'; import { MenuIcon, MenuItem, MenuSub } from '@affine/component';
import { track } from '@affine/core/mixpanel';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { import {
ExportIcon, ExportIcon,
@@ -8,7 +9,7 @@ import {
ExportToPngIcon, ExportToPngIcon,
} from '@blocksuite/icons/rc'; } from '@blocksuite/icons/rc';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { transitionStyle } from './index.css'; import { transitionStyle } from './index.css';
@@ -115,6 +116,12 @@ export const Export = ({ exportHandler, className, pageMode }: ExportProps) => {
pageMode={pageMode} pageMode={pageMode}
/> />
); );
const handleExportMenuOpenChange = useCallback((open: boolean) => {
if (open) {
track.$.header.docOptions.export();
}
}, []);
return ( return (
<MenuSub <MenuSub
items={items} items={items}
@@ -127,6 +134,9 @@ export const Export = ({ exportHandler, className, pageMode }: ExportProps) => {
), ),
['data-testid' as string]: 'export-menu', ['data-testid' as string]: 'export-menu',
}} }}
subOptions={{
onOpenChange: handleExportMenuOpenChange,
}}
> >
{t.Export()} {t.Export()}
</MenuSub> </MenuSub>

View File

@@ -26,7 +26,7 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
if (editorRoot) { if (editorRoot) {
pageService = editorRoot.spec.getService<PageRootService>('affine:page'); pageService = editorRoot.spec.getService<PageRootService>('affine:page');
} }
track.$.header.share.export({ track.$.sharePanel.$.export({
type, type,
}); });
switch (type) { switch (type) {

View File

@@ -68,7 +68,7 @@ export const useSharingUrl = ({
.catch(err => { .catch(err => {
console.error(err); console.error(err);
}); });
track.$.header.share.copyShareLink({ track.$.sharePanel.$.copyShareLink({
type: urlType === 'share' ? 'public' : 'private', type: urlType === 'share' ? 'public' : 'private',
}); });
} else { } else {

View File

@@ -35,7 +35,13 @@ type DocEvents =
| 'renameDoc' | 'renameDoc'
| 'linkDoc' | 'linkDoc'
| 'deleteDoc' | 'deleteDoc'
| 'switchPageMode'; | 'switchPageMode'
| 'openDocOptionsMenu'
| 'openDocInfo'
| 'renameDoc'
| 'deleteDoc'
| 'viewHistoryVersions'
| 'viewInfo';
type EditorEvents = 'bold' | 'italic' | 'underline' | 'strikeThrough'; type EditorEvents = 'bold' | 'italic' | 'underline' | 'strikeThrough';
// END SECTION // END SECTION
@@ -75,7 +81,11 @@ type OrganizeEvents =
// END SECTION // END SECTION
// SECTION: cloud events // SECTION: cloud events
type ShareEvents = 'createShareLink' | 'copyShareLink'; type ShareEvents =
| 'createShareLink'
| 'copyShareLink'
| 'openShareMenu'
| 'share';
type AuthEvents = 'signIn' | 'signUp' | 'oauth' | 'signOut'; type AuthEvents = 'signIn' | 'signUp' | 'oauth' | 'signOut';
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName'; type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
type PaymentEvents = type PaymentEvents =
@@ -127,6 +137,12 @@ const PageEvents = {
$: ['createWorkspace', 'checkout'], $: ['createWorkspace', 'checkout'],
auth: ['oauth', 'signIn', 'signUp'], auth: ['oauth', 'signIn', 'signUp'],
}, },
sharePanel: {
$: ['createShareLink', 'copyShareLink', 'export', 'open'],
},
docInfoPanel: {
$: ['open'],
},
settingsPanel: { settingsPanel: {
menu: ['openSettings'], menu: ['openSettings'],
workspace: ['viewPlans'], workspace: ['viewPlans'],
@@ -194,8 +210,26 @@ const PageEvents = {
aiAction: ['viewPlans'], aiAction: ['viewPlans'],
}, },
header: { header: {
actions: ['createDoc', 'createWorkspace', 'switchPageMode'], actions: [
share: ['createShareLink', 'copyShareLink', 'export'], 'createDoc',
'createWorkspace',
'switchPageMode',
'toggleFavorite',
'openDocInfo',
'renameDoc',
],
docOptions: [
'open',
'deleteDoc',
'renameDoc',
'switchPageMode',
'createDoc',
'import',
'toggleFavorite',
'export',
],
history: ['open'],
pageInfo: ['open'],
}, },
}, },
doc: { doc: {

View File

@@ -15,6 +15,7 @@ import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite
import { useRegisterCopyLinkCommands } from '@affine/core/hooks/affine/use-register-copy-link-commands'; import { useRegisterCopyLinkCommands } from '@affine/core/hooks/affine/use-register-copy-link-commands';
import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title'; import { useDocCollectionPageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal'; import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
import { track } from '@affine/core/mixpanel';
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
import type { Doc } from '@blocksuite/store'; import type { Doc } from '@blocksuite/store';
import { import {
@@ -129,6 +130,9 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
const title = useDocCollectionPageTitle(workspace.docCollection, page?.id); const title = useDocCollectionPageTitle(workspace.docCollection, page?.id);
const doc = useService(DocService).doc; const doc = useService(DocService).doc;
const currentMode = useLiveData(doc.mode$); const currentMode = useLiveData(doc.mode$);
const onEditSave = useCallback(() => {
track.$.header.actions.renameDoc();
}, []);
return ( return (
<Header className={styles.header} ref={containerRef}> <Header className={styles.header} ref={containerRef}>
@@ -142,6 +146,7 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {
inputHandleRef={titleInputHandleRef} inputHandleRef={titleInputHandleRef}
pageId={page?.id} pageId={page?.id}
docCollection={workspace.docCollection} docCollection={workspace.docCollection}
onEditSave={onEditSave}
/> />
<div className={styles.iconButtonContainer}> <div className={styles.iconButtonContainer}>
{hideCollect ? null : ( {hideCollect ? null : (