feat(core): enable new all docs by default (#12404)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Simplified the user interface by always displaying the new All Pages view, removing the feature flag and old page version.
  - Updated selection interactions to use shift+click on document items instead of checkboxes.
  - Centralized drag-and-drop functionality in document list items and simplified drag handle behavior.
  - Generalized new page button component to accept standard HTML attributes.
  - Changed test ID attributes on new page buttons and list headers to use standard `data-testid`.

- **Bug Fixes**
  - Added stable test identifiers to new page buttons, document list items, menu items, and operation buttons for improved test reliability.
  - Enabled external drag-and-drop support on the trash button.

- **Tests**
  - Streamlined and updated end-to-end tests to match the new selection flow and UI changes, removing outdated or redundant test cases.
  - Simplified utility functions and wait conditions in test helpers for better accuracy and maintainability.
  - Updated selectors in tests to reflect new document item identifiers and centralized page element retrieval using utility functions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-05-22 11:29:05 +00:00
parent 333dc9cb89
commit 5035ab218d
28 changed files with 212 additions and 398 deletions

View File

@@ -15,6 +15,8 @@ const DefaultDisplayPreference: ExplorerDisplayPreference = {
showDocIcon: true,
showDocPreview: true,
quickFavorite: true,
showDragHandle: true,
showMoreOperation: true,
};
export type DocExplorerContextType = {
@@ -85,5 +87,11 @@ export const createDocExplorerContext = (
quickTab$: displayPreference$.selector(
displayPreference => displayPreference.quickTab
),
showMoreOperation$: displayPreference$.selector(
displayPreference => displayPreference.showMoreOperation
),
showDragHandle$: displayPreference$.selector(
displayPreference => displayPreference.showDragHandle
),
} satisfies DocExplorerContextType;
};

View File

@@ -8,7 +8,6 @@ import { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { WorkbenchLink } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n';
import {
AutoTidyUpIcon,
PropertyIcon,
@@ -146,20 +145,45 @@ export const DocListItem = ({ ...props }: DocListItemProps) => {
[contextValue, handleMultiSelect, prevCheckAnchorId, props, selectMode]
);
const { dragRef, CustomDragPreview } = useDraggable<AffineDNDData>(
() => ({
canDrag: true,
data: {
entity: {
type: 'doc',
id: props.docId as string,
},
from: {
at: 'all-docs:list',
},
},
}),
[props.docId]
);
return (
<WorkbenchLink
draggable={false}
to={`/${props.docId}`}
onClick={handleClick}
data-selected={selectedDocIds.includes(props.docId)}
className={styles.root}
>
{view === 'list' ? (
<ListViewDoc {...props} />
) : (
<CardViewDoc {...props} />
)}
</WorkbenchLink>
<>
<WorkbenchLink
ref={dragRef}
draggable={false}
to={`/${props.docId}`}
onClick={handleClick}
data-selected={selectedDocIds.includes(props.docId)}
className={styles.root}
data-testid={`doc-list-item`}
data-doc-id={props.docId}
>
{view === 'list' ? (
<ListViewDoc {...props} />
) : (
<CardViewDoc {...props} />
)}
</WorkbenchLink>
<CustomDragPreview>
<RawDocIcon id={props.docId} />
<RawDocTitle id={props.docId} />
</CustomDragPreview>
</>
);
};
@@ -172,10 +196,9 @@ const RawDocIcon = memo(function RawDocIcon({
return <Icon {...props} />;
});
const RawDocTitle = memo(function RawDocTitle({ id }: { id: string }) {
const i18n = useI18n();
const docDisplayMetaService = useService(DocDisplayMetaService);
const title = useLiveData(docDisplayMetaService.title$(id));
return i18n.t(title);
return title;
});
const RawDocPreview = memo(function RawDocPreview({
id,
@@ -188,47 +211,20 @@ const RawDocPreview = memo(function RawDocPreview({
});
const DragHandle = memo(function DragHandle({
id,
preview,
...props
}: HTMLProps<HTMLDivElement> & { preview?: ReactNode }) {
}: HTMLProps<HTMLDivElement>) {
const contextValue = useContext(DocExplorerContext);
const selectMode = useLiveData(contextValue.selectMode$);
const showDragHandle = useLiveData(contextValue.showDragHandle$);
const { dragRef, CustomDragPreview } = useDraggable<AffineDNDData>(
() => ({
canDrag: true,
data: {
entity: {
type: 'doc',
id: id as string,
},
from: {
at: 'all-docs:list',
},
},
}),
[id]
);
if (selectMode || !id || !showDragHandle) {
return null;
}
return (
<>
<div ref={dragRef} {...props}>
<DragHandleIcon />
</div>
<CustomDragPreview>
{preview ?? (
<>
<RawDocIcon id={id} />
<RawDocTitle id={id} />
</>
)}
</CustomDragPreview>
</>
<div {...props}>
<DragHandleIcon />
</div>
);
});
const Select = memo(function Select({
@@ -248,7 +244,11 @@ const Select = memo(function Select({
}
return (
<div data-select-mode={selectMode} {...props}>
<div
data-select-mode={selectMode}
data-testid={`doc-list-item-select`}
{...props}
>
<Checkbox
checked={selectedDocIds.includes(id)}
onChange={handleSelectChange}
@@ -323,7 +323,11 @@ export const ListViewDoc = ({ docId }: DocListItemProps) => {
<Select id={docId} className={styles.listSelect} />
<DocIcon id={docId} className={styles.listIcon} />
<div className={styles.listBrief}>
<DocTitle id={docId} className={styles.listTitle} />
<DocTitle
id={docId}
className={styles.listTitle}
data-testid="doc-list-item-title"
/>
<DocPreview
id={docId}
className={styles.listPreview}
@@ -370,7 +374,11 @@ export const CardViewDoc = ({ docId }: DocListItemProps) => {
<li className={styles.cardViewRoot}>
<header className={styles.cardViewHeader}>
<DocIcon id={docId} className={styles.cardViewIcon} />
<DocTitle id={docId} className={styles.cardViewTitle} />
<DocTitle
id={docId}
className={styles.cardViewTitle}
data-testid="doc-list-item-title"
/>
{quickActions.map(action => {
return <action.Component size="16" key={action.key} doc={doc} />;
})}

View File

@@ -47,6 +47,7 @@ const ToggleFavorite = ({ docId }: DocOperationProps) => {
<MenuItem
prefixIcon={<IsFavoriteIcon favorite={favourite} />}
onClick={toggleFavorite}
data-testid="doc-list-operation-favorite"
>
{favourite
? t['com.affine.favoritePageOperation.remove']()
@@ -107,7 +108,7 @@ const SplitView = ({ docId }: DocOperationProps) => {
return (
<MenuItem onClick={onOpenInSplitView} prefixIcon={<SplitViewIcon />}>
{t['com.affine.workbench.tab.page-menu-open']()}
{t['com.affine.workbench.split-view.page-menu-open']()}
</MenuItem>
);
};
@@ -165,7 +166,11 @@ const MoveToTrash = ({ docId }: DocOperationProps) => {
}, [doc, openConfirmModal, t]);
return (
<MenuItem prefixIcon={<DeleteIcon />} onClick={onMoveToTrash}>
<MenuItem
prefixIcon={<DeleteIcon />}
data-testid="doc-list-operation-trash"
onClick={onMoveToTrash}
>
{t['com.affine.moveToTrash.title']()}
</MenuItem>
);
@@ -187,10 +192,22 @@ export const MoreMenuContent = (props: DocOperationProps) => {
export const MoreMenu = ({
docId,
children,
contentOptions,
...menuProps
}: Omit<MenuProps, 'items'> & { docId: string }) => {
return (
<Menu items={<MoreMenuContent docId={docId} />} {...menuProps}>
<Menu
items={<MoreMenuContent docId={docId} />}
contentOptions={{
...contentOptions,
onClick: e => {
// prevent external click events from being triggered
e.stopPropagation();
contentOptions?.onClick?.(e);
},
}}
{...menuProps}
>
{children}
</Menu>
);
@@ -213,7 +230,11 @@ export const MoreMenuButton = ({
return (
<MoreMenu docId={docId} {...menuProps}>
<IconButton icon={<MoreVerticalIcon />} {...iconProps} />
<IconButton
data-testid="doc-list-operation-button"
icon={<MoreVerticalIcon />}
{...iconProps}
/>
</MoreMenu>
);
};

View File

@@ -47,6 +47,7 @@ export const QuickFavorite = memo(function QuickFavorite({
<IconButton
icon={<IsFavoriteIcon favorite={favourite} />}
onClick={toggleFavorite}
data-testid="doc-list-operation-favorite"
{...iconButtonProps}
/>
);
@@ -157,6 +158,7 @@ export const QuickDelete = memo(function QuickDelete({
onClick={onMoveToTrash}
icon={<DeleteIcon />}
variant="danger"
data-testid="doc-list-operation-trash"
{...iconButtonProps}
/>
);

View File

@@ -92,7 +92,7 @@ export const PageListHeader = () => {
<div className={styles.docListHeaderTitle}>{title}</div>
<PageListNewPageButton
size="small"
testId="new-page-button-trigger"
data-testid="new-page-button-trigger"
onCreateEdgeless={e => createEdgeless({ at: inferOpenMode(e) })}
onCreatePage={e =>
createPage('page' as DocMode, { at: inferOpenMode(e) })
@@ -194,7 +194,7 @@ export const CollectionPageListHeader = ({
<Button onClick={handleEdit}>{t['Edit']()}</Button>
<PageListNewPageButton
size="small"
testId="new-page-button-trigger"
data-testid="new-page-button-trigger"
onCreateDoc={onCreateDoc}
onCreateEdgeless={onCreateEdgeless}
onCreatePage={onCreatePage}

View File

@@ -7,22 +7,22 @@ export const PageListNewPageButton = ({
className,
children,
size,
testId,
onCreateDoc,
onCreatePage,
onCreateEdgeless,
onImportFile,
...props
}: PropsWithChildren<{
className?: string;
size?: 'small' | 'default';
testId?: string;
onCreateDoc: (e?: MouseEvent) => void;
onCreatePage: (e?: MouseEvent) => void;
onCreateEdgeless: (e?: MouseEvent) => void;
onImportFile?: (e?: MouseEvent) => void;
}>) => {
}> &
React.HTMLAttributes<HTMLDivElement>) => {
return (
<div className={className} data-testid={testId}>
<div className={className} {...props}>
<NewPageButton
size={size}
importFile={onImportFile}

View File

@@ -68,6 +68,7 @@ export const TrashButton = () => {
}
}
},
allowExternal: true,
}),
[docsService.list, guardService, openConfirmModal, t]
);

View File

@@ -104,6 +104,7 @@ export const AllDocsHeader = ({
onCreatePage={e => createPage('page', { at: inferOpenMode(e) })}
onCreateDoc={e => createPage(undefined, { at: inferOpenMode(e) })}
onImportFile={onImportFile}
data-testid="new-page-button-trigger"
>
<span className={styles.newPageButtonLabel}>{t['New Page']()}</span>
</PageListNewPageButton>

View File

@@ -12,7 +12,6 @@ import {
} from '@affine/core/modules/collection';
import { CollectionRulesService } from '@affine/core/modules/collection-rules';
import type { FilterParams } from '@affine/core/modules/collection-rules/types';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { WorkspaceLocalState } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
@@ -24,7 +23,6 @@ import {
ViewIcon,
ViewTitle,
} from '../../../../modules/workbench';
import { AllPage as AllPageOld } from '../all-page-old/all-page';
import { AllDocSidebarTabs } from '../layouts/all-doc-sidebar-tabs';
import * as styles from './all-page.css';
import { AllDocsHeader } from './all-page-header';
@@ -350,10 +348,5 @@ export const AllPage = () => {
};
export const Component = () => {
const featureFlagService = useService(FeatureFlagService);
const enableNewAllDocsPage = useLiveData(
featureFlagService.flags.enable_new_all_docs_page.$
);
return enableNewAllDocsPage ? <AllPage /> : <AllPageOld />;
return <AllPage />;
};

View File

@@ -100,7 +100,7 @@ export const CollectionListHeader = ({
<Button onClick={handleEdit}>{t['Edit']()}</Button>
<PageListNewPageButton
size="small"
testId="new-page-button-trigger"
data-testid="new-page-button-trigger"
onCreateDoc={onCreateDoc}
onCreateEdgeless={onCreateEdgeless}
onCreatePage={onCreatePage}

View File

@@ -311,13 +311,6 @@ export const AFFINE_FLAGS = {
configurable: false,
defaultState: isCanaryBuild,
},
enable_new_all_docs_page: {
category: 'affine',
displayName: 'Enable New All Docs Page',
description: 'Use new all docs page',
configurable: isCanaryBuild,
defaultState: false,
},
enable_cloud_indexer: {
category: 'affine',
displayName: 'Enable Cloud Indexer',