feat(mobile): add delete operation for detail page menu (#12900)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a "Move to Trash" option in the page header menu, allowing users to move documents to trash with confirmation and permission checks. - **Refactor** - Centralized and reorganized tab-related type definitions for improved maintainability. - Updated tab components to use shared constants and types. - **Style** - Updated menu item styling for the new trash action to indicate a destructive operation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
934e377054
commit
24b205ae83
@ -0,0 +1 @@
|
||||
export const cacheKey = 'activeAppTabId';
|
@ -8,8 +8,8 @@ import track from '@affine/track';
|
||||
import { EditIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
|
||||
import type { AppTabCustomFCProps } from './data';
|
||||
import { TabItem } from './tab-item';
|
||||
import type { AppTabCustomFCProps } from './type';
|
||||
|
||||
export const AppTabCreate = ({ tab }: AppTabCustomFCProps) => {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
|
@ -1,28 +1,8 @@
|
||||
import { AllDocsIcon, HomeIcon } from '@blocksuite/icons/rc';
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
import { AppTabCreate } from './create';
|
||||
import { AppTabJournal } from './journal';
|
||||
|
||||
interface AppTabBase {
|
||||
key: string;
|
||||
onClick?: (framework: Framework, isActive: boolean) => void;
|
||||
}
|
||||
export interface AppTabLink extends AppTabBase {
|
||||
Icon: React.FC;
|
||||
to: string;
|
||||
LinkComponent?: React.FC;
|
||||
}
|
||||
|
||||
export interface AppTabCustom extends AppTabBase {
|
||||
custom: (props: AppTabCustomFCProps) => React.ReactNode;
|
||||
}
|
||||
|
||||
export type Tab = AppTabLink | AppTabCustom;
|
||||
|
||||
export interface AppTabCustomFCProps {
|
||||
tab: Tab;
|
||||
}
|
||||
import type { Tab } from './type';
|
||||
|
||||
export const tabs: Tab[] = [
|
||||
{
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { SafeArea } from '@affine/component';
|
||||
import { WorkbenchLink } from '@affine/core/modules/workbench';
|
||||
import { GlobalCacheService } from '@affine/core/modules/storage';
|
||||
import {
|
||||
WorkbenchLink,
|
||||
WorkbenchService,
|
||||
} from '@affine/core/modules/workbench';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { VirtualKeyboardService } from '../../modules/virtual-keyboard/services/virtual-keyboard';
|
||||
import { type AppTabLink, tabs } from './data';
|
||||
import { cacheKey } from './constants';
|
||||
import { tabs } from './data';
|
||||
import * as styles from './styles.css';
|
||||
import { TabItem } from './tab-item';
|
||||
import type { AppTabLink } from './type';
|
||||
|
||||
export const AppTabs = ({
|
||||
background,
|
||||
@ -19,6 +25,16 @@ export const AppTabs = ({
|
||||
}) => {
|
||||
const virtualKeyboardService = useService(VirtualKeyboardService);
|
||||
const virtualKeyboardVisible = useLiveData(virtualKeyboardService.visible$);
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const location = useLiveData(workbench.location$);
|
||||
const globalCache = useService(GlobalCacheService).globalCache;
|
||||
|
||||
// always set the active tab to home when the location is changed to home
|
||||
useEffect(() => {
|
||||
if (location.pathname === '/home') {
|
||||
globalCache.set(cacheKey, 'home');
|
||||
}
|
||||
}, [globalCache, location.pathname]);
|
||||
|
||||
const tab = (
|
||||
<SafeArea
|
||||
|
@ -5,8 +5,8 @@ import { TodayIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type { AppTabCustomFCProps } from './data';
|
||||
import { TabItem } from './tab-item';
|
||||
import type { AppTabCustomFCProps } from './type';
|
||||
|
||||
export const AppTabJournal = ({ tab }: AppTabCustomFCProps) => {
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { GlobalCacheService } from '@affine/core/modules/storage';
|
||||
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import { type PropsWithChildren, useCallback, useEffect, useMemo } from 'react';
|
||||
import { type PropsWithChildren, useCallback, useMemo } from 'react';
|
||||
|
||||
import { cacheKey } from './constants';
|
||||
import { tabItem } from './styles.css';
|
||||
|
||||
export interface TabItemProps extends PropsWithChildren {
|
||||
@ -10,8 +11,6 @@ export interface TabItemProps extends PropsWithChildren {
|
||||
onClick?: (isActive: boolean) => void;
|
||||
}
|
||||
|
||||
const cacheKey = 'activeAppTabId';
|
||||
let isInitialized = false;
|
||||
export const TabItem = ({ id, label, children, onClick }: TabItemProps) => {
|
||||
const globalCache = useService(GlobalCacheService).globalCache;
|
||||
const activeTabId$ = useMemo(
|
||||
@ -27,14 +26,6 @@ export const TabItem = ({ id, label, children, onClick }: TabItemProps) => {
|
||||
onClick?.(isActive);
|
||||
}, [globalCache, id, isActive, onClick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) return;
|
||||
isInitialized = true;
|
||||
if (BUILD_CONFIG.isIOS || BUILD_CONFIG.isAndroid) {
|
||||
globalCache.set(cacheKey, 'home');
|
||||
}
|
||||
}, [globalCache]);
|
||||
|
||||
return (
|
||||
<li
|
||||
className={tabItem}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import type { Framework } from '@toeverything/infra';
|
||||
|
||||
interface AppTabBase {
|
||||
key: string;
|
||||
onClick?: (framework: Framework, isActive: boolean) => void;
|
||||
}
|
||||
export interface AppTabLink extends AppTabBase {
|
||||
Icon: React.FC;
|
||||
to: string;
|
||||
LinkComponent?: React.FC;
|
||||
}
|
||||
|
||||
export interface AppTabCustom extends AppTabBase {
|
||||
custom: (props: AppTabCustomFCProps) => React.ReactNode;
|
||||
}
|
||||
|
||||
export type Tab = AppTabLink | AppTabCustom;
|
||||
|
||||
export interface AppTabCustomFCProps {
|
||||
tab: Tab;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { IconButton, notify } from '@affine/component';
|
||||
import { IconButton, notify, toast, useConfirmModal } from '@affine/component';
|
||||
import {
|
||||
MenuSeparator,
|
||||
MenuSub,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
MobileMenuItem,
|
||||
} from '@affine/component/ui/menu';
|
||||
import { useFavorite } from '@affine/core/blocksuite/block-suite-header/favorite';
|
||||
import { useGuard } from '@affine/core/components/guard';
|
||||
import { Guard, useGuard } from '@affine/core/components/guard';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { DocInfoSheet } from '@affine/core/mobile/components';
|
||||
import { MobileTocMenu } from '@affine/core/mobile/components/toc-menu';
|
||||
@ -17,6 +17,7 @@ import { preventDefault } from '@affine/core/utils';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import {
|
||||
DeleteIcon,
|
||||
EdgelessIcon,
|
||||
InformationIcon,
|
||||
MoreHorizontalIcon,
|
||||
@ -34,7 +35,8 @@ import * as styles from './page-header-more-button.css';
|
||||
export const PageHeaderMenuButton = () => {
|
||||
const t = useI18n();
|
||||
|
||||
const docId = useService(DocService).doc.id;
|
||||
const doc = useService(DocService).doc;
|
||||
const docId = doc?.id;
|
||||
const canEdit = useGuard('Doc_Update', docId);
|
||||
|
||||
const editorService = useService(EditorService);
|
||||
@ -50,6 +52,7 @@ export const PageHeaderMenuButton = () => {
|
||||
const title = useLiveData(editorService.editor.doc.title$);
|
||||
|
||||
const { favorite, toggleFavorite } = useFavorite(docId);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
|
||||
const handleSwitchMode = useCallback(() => {
|
||||
const mode = primaryMode === 'page' ? 'edgeless' : 'page';
|
||||
@ -88,6 +91,32 @@ export const PageHeaderMenuButton = () => {
|
||||
toggleFavorite();
|
||||
}, [toggleFavorite]);
|
||||
|
||||
const handleMoveToTrash = useCallback(() => {
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
openConfirmModal({
|
||||
title: t['com.affine.moveToTrash.title'](),
|
||||
description: t['com.affine.moveToTrash.confirmModal.description']({
|
||||
title: doc.title$.value,
|
||||
}),
|
||||
confirmText: t['com.affine.moveToTrash.confirmModal.confirm'](),
|
||||
cancelText: t['com.affine.moveToTrash.confirmModal.cancel'](),
|
||||
confirmButtonOptions: {
|
||||
variant: 'error',
|
||||
},
|
||||
onConfirm() {
|
||||
doc.moveToTrash();
|
||||
track.$.navigationPanel.docs.deleteDoc({
|
||||
control: 'button',
|
||||
});
|
||||
toast(t['com.affine.toastMessage.movedTrash']());
|
||||
// navigate back
|
||||
history.back();
|
||||
},
|
||||
});
|
||||
}, [doc, openConfirmModal, t]);
|
||||
|
||||
const EditMenu = (
|
||||
<>
|
||||
<EditorModeSwitch />
|
||||
@ -135,6 +164,18 @@ export const PageHeaderMenuButton = () => {
|
||||
</MobileMenuItem>
|
||||
</MobileMenu>
|
||||
<JournalConflictsMenuItem />
|
||||
<Guard docId={docId} permission="Doc_Trash">
|
||||
{canMoveToTrash => (
|
||||
<MobileMenuItem
|
||||
prefixIcon={<DeleteIcon />}
|
||||
type="danger"
|
||||
disabled={!canMoveToTrash}
|
||||
onSelect={handleMoveToTrash}
|
||||
>
|
||||
{t['com.affine.moveToTrash.title']()}
|
||||
</MobileMenuItem>
|
||||
)}
|
||||
</Guard>
|
||||
</>
|
||||
);
|
||||
if (isInTrash) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user