mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 21:06:22 +08:00
@@ -9,7 +9,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { selectAtom } from 'jotai/utils';
|
import { selectAtom } from 'jotai/utils';
|
||||||
import type { MouseEventHandler } from 'react';
|
import type { MouseEventHandler } from 'react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { CollectionListItem } from './collections/collection-list-item';
|
import { CollectionListItem } from './collections/collection-list-item';
|
||||||
import { PageListItem } from './docs/page-list-item';
|
import { PageListItem } from './docs/page-list-item';
|
||||||
@@ -240,7 +240,7 @@ export const PageListItemRenderer = (item: ListItem) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CollectionListItemRenderer = (item: ListItem) => {
|
export const CollectionListItemRenderer = memo((item: ListItem) => {
|
||||||
const props = useAtomValue(listsPropsAtom);
|
const props = useAtomValue(listsPropsAtom);
|
||||||
const { selectionActive } = useAtomValue(selectionStateAtom);
|
const { selectionActive } = useAtomValue(selectionStateAtom);
|
||||||
const collection = item as CollectionMeta;
|
const collection = item as CollectionMeta;
|
||||||
@@ -252,7 +252,9 @@ export const CollectionListItemRenderer = (item: ListItem) => {
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
CollectionListItemRenderer.displayName = 'CollectionListItemRenderer';
|
||||||
|
|
||||||
export const TagListItemRenderer = (item: ListItem) => {
|
export const TagListItemRenderer = (item: ListItem) => {
|
||||||
const props = useAtomValue(listsPropsAtom);
|
const props = useAtomValue(listsPropsAtom);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RadioGroup, type RadioItem } from '@affine/component';
|
import { RadioGroup, type RadioItem } from '@affine/component';
|
||||||
import type { AllPageFilterOption } from '@affine/core/components/atoms';
|
import type { AllPageFilterOption } from '@affine/core/components/atoms';
|
||||||
import { allPageFilterSelectAtom } from '@affine/core/components/atoms';
|
import { allPageFilterSelectAtom } from '@affine/core/components/atoms';
|
||||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
@@ -14,26 +14,25 @@ export const WorkspaceModeFilterTab = ({
|
|||||||
}: {
|
}: {
|
||||||
activeFilter: AllPageFilterOption;
|
activeFilter: AllPageFilterOption;
|
||||||
}) => {
|
}) => {
|
||||||
const workspace = useService(WorkspaceService).workspace;
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [value, setValue] = useState(activeFilter);
|
const [value, setValue] = useState(activeFilter);
|
||||||
const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom);
|
const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom);
|
||||||
const { jumpToCollections, jumpToTags, jumpToPage } = useNavigateHelper();
|
const workbenchService = useService(WorkbenchService);
|
||||||
const handleValueChange = useCallback(
|
const handleValueChange = useCallback(
|
||||||
(value: AllPageFilterOption) => {
|
(value: AllPageFilterOption) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 'collections':
|
case 'collections':
|
||||||
jumpToCollections(workspace.id);
|
workbenchService.workbench.openCollections();
|
||||||
break;
|
break;
|
||||||
case 'tags':
|
case 'tags':
|
||||||
jumpToTags(workspace.id);
|
workbenchService.workbench.openTags();
|
||||||
break;
|
break;
|
||||||
case 'docs':
|
case 'docs':
|
||||||
jumpToPage(workspace.id, 'all');
|
workbenchService.workbench.openAll();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[jumpToCollections, jumpToPage, jumpToTags, workspace]
|
[workbenchService.workbench]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
|
||||||
import {
|
import {
|
||||||
AddPageButton,
|
AddPageButton,
|
||||||
AppDownloadButton,
|
AppDownloadButton,
|
||||||
@@ -23,7 +22,6 @@ import {
|
|||||||
} from '@affine/core/modules/explorer';
|
} from '@affine/core/modules/explorer';
|
||||||
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
import { ExplorerTags } from '@affine/core/modules/explorer/views/sections/tags';
|
||||||
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
import { CMDKQuickSearchService } from '@affine/core/modules/quicksearch/services/cmdk';
|
||||||
import { isNewTabTrigger } 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 type { Doc } from '@blocksuite/affine/store';
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
@@ -35,17 +33,11 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import type { Workspace } from '@toeverything/infra';
|
import type { Workspace } from '@toeverything/infra';
|
||||||
import {
|
import { useLiveData, useService, useServices } from '@toeverything/infra';
|
||||||
useLiveData,
|
import type { ReactElement } from 'react';
|
||||||
useService,
|
import { memo, useCallback } from 'react';
|
||||||
useServices,
|
|
||||||
WorkspaceService,
|
|
||||||
} from '@toeverything/infra';
|
|
||||||
import type { MouseEvent, ReactElement } from 'react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { WorkbenchService } from '../../modules/workbench';
|
import { WorkbenchService } from '../../modules/workbench';
|
||||||
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
|
|
||||||
import { WorkspaceNavigator } from '../workspace-selector';
|
import { WorkspaceNavigator } from '../workspace-selector';
|
||||||
import {
|
import {
|
||||||
quickSearch,
|
quickSearch,
|
||||||
@@ -72,42 +64,43 @@ export type RootAppSidebarProps = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AllDocsButton = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { workbenchService } = useServices({
|
||||||
|
WorkbenchService,
|
||||||
|
});
|
||||||
|
const workbench = workbenchService.workbench;
|
||||||
|
const allPageActive = useLiveData(
|
||||||
|
workbench.location$.selector(location => location.pathname === '/all')
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuLinkItem icon={<AllDocsIcon />} active={allPageActive} to={'/all'}>
|
||||||
|
<span data-testid="all-pages">
|
||||||
|
{t['com.affine.workspaceSubPath.all']()}
|
||||||
|
</span>
|
||||||
|
</MenuLinkItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is for the whole affine app sidebar.
|
* This is for the whole affine app sidebar.
|
||||||
* This component wraps the app sidebar in `@affine/component` with logic and data.
|
* This component wraps the app sidebar in `@affine/component` with logic and data.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const RootAppSidebar = (): ReactElement => {
|
export const RootAppSidebar = memo((): ReactElement => {
|
||||||
const { workbenchService, workspaceService, cMDKQuickSearchService } =
|
const { workbenchService, cMDKQuickSearchService } = useServices({
|
||||||
useServices({
|
WorkbenchService,
|
||||||
WorkspaceService,
|
CMDKQuickSearchService,
|
||||||
WorkbenchService,
|
});
|
||||||
CMDKQuickSearchService,
|
|
||||||
});
|
|
||||||
const currentWorkspace = workspaceService.workspace;
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const globalDialogService = useService(GlobalDialogService);
|
const globalDialogService = useService(GlobalDialogService);
|
||||||
const workspaceDialogService = useService(WorkspaceDialogService);
|
const workspaceDialogService = useService(WorkspaceDialogService);
|
||||||
const workbench = workbenchService.workbench;
|
const workbench = workbenchService.workbench;
|
||||||
const currentPath = useLiveData(
|
|
||||||
workbench.location$.map(location => location.pathname)
|
|
||||||
);
|
|
||||||
const onOpenQuickSearchModal = useCallback(() => {
|
const onOpenQuickSearchModal = useCallback(() => {
|
||||||
cMDKQuickSearchService.toggle();
|
cMDKQuickSearchService.toggle();
|
||||||
}, [cMDKQuickSearchService]);
|
}, [cMDKQuickSearchService]);
|
||||||
|
|
||||||
const allPageActive = currentPath === '/all';
|
|
||||||
|
|
||||||
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
|
||||||
|
|
||||||
const onClickNewPage = useAsyncCallback(
|
|
||||||
async (e?: MouseEvent) => {
|
|
||||||
pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true);
|
|
||||||
track.$.navigationPanel.$.createDoc();
|
|
||||||
},
|
|
||||||
[pageHelper]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onOpenSettingModal = useCallback(() => {
|
const onOpenSettingModal = useCallback(() => {
|
||||||
globalDialogService.open('setting', {
|
globalDialogService.open('setting', {
|
||||||
activeTab: 'appearance',
|
activeTab: 'appearance',
|
||||||
@@ -169,13 +162,9 @@ export const RootAppSidebar = (): ReactElement => {
|
|||||||
data-event-props="$.navigationPanel.$.quickSearch"
|
data-event-props="$.navigationPanel.$.quickSearch"
|
||||||
onClick={onOpenQuickSearchModal}
|
onClick={onOpenQuickSearchModal}
|
||||||
/>
|
/>
|
||||||
<AddPageButton onClick={onClickNewPage} />
|
<AddPageButton />
|
||||||
</div>
|
</div>
|
||||||
<MenuLinkItem icon={<AllDocsIcon />} active={allPageActive} to={'/all'}>
|
<AllDocsButton />
|
||||||
<span data-testid="all-pages">
|
|
||||||
{t['com.affine.workspaceSubPath.all']()}
|
|
||||||
</span>
|
|
||||||
</MenuLinkItem>
|
|
||||||
<AppSidebarJournalButton />
|
<AppSidebarJournalButton />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-testid="slider-bar-workspace-setting-button"
|
data-testid="slider-bar-workspace-setting-button"
|
||||||
@@ -220,6 +209,6 @@ export const RootAppSidebar = (): ReactElement => {
|
|||||||
</SidebarContainer>
|
</SidebarContainer>
|
||||||
</AppSidebar>
|
</AppSidebar>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
RootAppSidebar.displayName = 'memo(RootAppSidebar)';
|
RootAppSidebar.displayName = 'memo(RootAppSidebar)';
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
|
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import track from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import type { MouseEventHandler } from 'react';
|
import { type MouseEvent, useCallback } from 'react';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
|
|
||||||
interface AddPageButtonProps {
|
interface AddPageButtonProps {
|
||||||
onClick?: MouseEventHandler;
|
|
||||||
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({
|
export function AddPageButton({ className, style }: AddPageButtonProps) {
|
||||||
onClick,
|
const workspaceService = useService(WorkspaceService);
|
||||||
className,
|
const currentWorkspace = workspaceService.workspace;
|
||||||
style,
|
const pageHelper = usePageHelper(currentWorkspace.docCollection);
|
||||||
}: AddPageButtonProps) {
|
|
||||||
|
const onClickNewPage = useCallback(
|
||||||
|
(e?: MouseEvent) => {
|
||||||
|
pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true);
|
||||||
|
track.$.navigationPanel.$.createDoc();
|
||||||
|
},
|
||||||
|
[pageHelper]
|
||||||
|
);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -28,8 +39,8 @@ export function AddPageButton({
|
|||||||
data-testid="sidebar-new-page-button"
|
data-testid="sidebar-new-page-button"
|
||||||
style={style}
|
style={style}
|
||||||
className={clsx([styles.root, className])}
|
className={clsx([styles.root, className])}
|
||||||
onClick={onClick}
|
onClick={onClickNewPage}
|
||||||
onAuxClick={onClick}
|
onAuxClick={onClickNewPage}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -89,6 +89,34 @@ interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {
|
|||||||
dropEffect?: ExplorerTreeNodeDropEffect;
|
dropEffect?: ExplorerTreeNodeDropEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* specific rename modal for explorer tree node,
|
||||||
|
* Separate it into a separate component to prevent re-rendering the entire component when width changes.
|
||||||
|
*/
|
||||||
|
const ExplorerTreeNodeRenameModal = ({
|
||||||
|
setRenaming,
|
||||||
|
handleRename,
|
||||||
|
rawName,
|
||||||
|
}: {
|
||||||
|
setRenaming: (renaming: boolean) => void;
|
||||||
|
handleRename: (newName: string) => void;
|
||||||
|
rawName: string | undefined;
|
||||||
|
}) => {
|
||||||
|
const appSidebarService = useService(AppSidebarService).sidebar;
|
||||||
|
const sidebarWidth = useLiveData(appSidebarService.width$);
|
||||||
|
return (
|
||||||
|
<RenameModal
|
||||||
|
open
|
||||||
|
width={sidebarWidth - 32}
|
||||||
|
onOpenChange={setRenaming}
|
||||||
|
onRename={handleRename}
|
||||||
|
currentName={rawName ?? ''}
|
||||||
|
>
|
||||||
|
<div className={styles.itemRenameAnchor} />
|
||||||
|
</RenameModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ExplorerTreeNode = ({
|
export const ExplorerTreeNode = ({
|
||||||
children,
|
children,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
@@ -126,9 +154,6 @@ export const ExplorerTreeNode = ({
|
|||||||
const [lastInGroup, setLastInGroup] = useState(false);
|
const [lastInGroup, setLastInGroup] = useState(false);
|
||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const appSidebarService = useService(AppSidebarService).sidebar;
|
|
||||||
const sidebarWidth = useLiveData(appSidebarService.width$);
|
|
||||||
|
|
||||||
const { emoji, name } = useMemo(() => {
|
const { emoji, name } = useMemo(() => {
|
||||||
if (!extractEmojiAsIcon || !rawName) {
|
if (!extractEmojiAsIcon || !rawName) {
|
||||||
return {
|
return {
|
||||||
@@ -379,16 +404,12 @@ export const ExplorerTreeNode = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{renameable && (
|
{renameable && renaming && (
|
||||||
<RenameModal
|
<ExplorerTreeNodeRenameModal
|
||||||
open={!!renaming}
|
setRenaming={setRenaming}
|
||||||
width={sidebarWidth - 32}
|
handleRename={handleRename}
|
||||||
onOpenChange={setRenaming}
|
rawName={rawName}
|
||||||
onRename={handleRename}
|
/>
|
||||||
currentName={rawName ?? ''}
|
|
||||||
>
|
|
||||||
<div className={styles.itemRenameAnchor} />
|
|
||||||
</RenameModal>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { View as WorkbenchView } from './entities/view';
|
export { View as WorkbenchView } from './entities/view';
|
||||||
export { Workbench } from './entities/workbench';
|
export { Workbench } from './entities/workbench';
|
||||||
export { ViewScope } from './scopes/view';
|
export { ViewScope } from './scopes/view';
|
||||||
|
export { ViewService } from './services/view';
|
||||||
export { WorkbenchService } from './services/workbench';
|
export { WorkbenchService } from './services/workbench';
|
||||||
export { useBindWorkbenchToBrowserRouter } from './view/browser-adapter';
|
export { useBindWorkbenchToBrowserRouter } from './view/browser-adapter';
|
||||||
export { useIsActiveView } from './view/use-is-active-view';
|
export { useIsActiveView } from './view/use-is-active-view';
|
||||||
|
|||||||
Reference in New Issue
Block a user