mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat(core): add confirm modal for delete tag action (#6268)
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
Scrollable,
|
||||
} from '@affine/component';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { DeleteTagConfirmModal, TagService } from '@affine/core/modules/tag';
|
||||
import { WorkspaceLegacyProperties } from '@affine/core/modules/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DeleteIcon, MoreHorizontalIcon, TagsIcon } from '@blocksuite/icons';
|
||||
@@ -74,8 +74,12 @@ const InlineTagsList = ({
|
||||
|
||||
export const EditTagMenu = ({
|
||||
tagId,
|
||||
onTagDelete,
|
||||
children,
|
||||
}: PropsWithChildren<{ tagId: string }>) => {
|
||||
}: PropsWithChildren<{
|
||||
tagId: string;
|
||||
onTagDelete: (tagIds: string[]) => void;
|
||||
}>) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const legacyProperties = useService(WorkspaceLegacyProperties);
|
||||
const tagService = useService(TagService);
|
||||
@@ -116,7 +120,7 @@ export const EditTagMenu = ({
|
||||
icon: <DeleteIcon />,
|
||||
type: 'danger',
|
||||
onClick() {
|
||||
tagService.deleteTag(tag?.id || '');
|
||||
onTagDelete([tag?.id || '']);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -164,10 +168,10 @@ export const EditTagMenu = ({
|
||||
}, [
|
||||
legacyProperties.workspaceId,
|
||||
navigate,
|
||||
onTagDelete,
|
||||
t,
|
||||
tag,
|
||||
tagColor,
|
||||
tagService,
|
||||
tagValue,
|
||||
]);
|
||||
|
||||
@@ -180,6 +184,24 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
|
||||
const tags = useLiveData(tagService.tags);
|
||||
const tagIds = useLiveData(tagService.tagIdsByPageId(pageId));
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
|
||||
|
||||
const handleCloseModal = useCallback(
|
||||
(open: boolean) => {
|
||||
setOpen(open);
|
||||
setSelectedTagIds([]);
|
||||
},
|
||||
[setOpen]
|
||||
);
|
||||
|
||||
const onTagDelete = useCallback(
|
||||
(tagIds: string[]) => {
|
||||
setOpen(true);
|
||||
setSelectedTagIds(tagIds);
|
||||
},
|
||||
[setOpen, setSelectedTagIds]
|
||||
);
|
||||
|
||||
const exactMatch = useLiveData(tagService.tagByTagValue(inputValue));
|
||||
|
||||
@@ -280,7 +302,7 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
|
||||
>
|
||||
<TagItem maxWidth="100%" tag={tag} mode="inline" />
|
||||
<div className={styles.spacer} />
|
||||
<EditTagMenu tagId={tag.id}>
|
||||
<EditTagMenu tagId={tag.id} onTagDelete={onTagDelete}>
|
||||
<IconButton
|
||||
className={styles.tagEditIcon}
|
||||
type="plain"
|
||||
@@ -307,6 +329,11 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
|
||||
<Scrollable.Scrollbar style={{ transform: 'translateX(6px)' }} />
|
||||
</Scrollable.Root>
|
||||
</div>
|
||||
<DeleteTagConfirmModal
|
||||
open={open}
|
||||
onOpenChange={handleCloseModal}
|
||||
selectedTagIds={selectedTagIds}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Menu,
|
||||
MenuIcon,
|
||||
MenuItem,
|
||||
toast,
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
|
||||
@@ -334,8 +333,7 @@ export const TagOperationCell = ({
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onTagDelete([tag.id]);
|
||||
toast(t['com.affine.tags.delete-tags.toast']());
|
||||
}, [onTagDelete, t, tag.id]);
|
||||
}, [onTagDelete, tag.id]);
|
||||
return (
|
||||
<>
|
||||
<div className={styles.editTagWrapper} data-show={open}>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { toast } from '@affine/component';
|
||||
import type { Tag } from '@affine/core/modules/tag';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
@@ -25,7 +23,6 @@ export const VirtualizedTagList = ({
|
||||
tagMetas: TagMeta[];
|
||||
onTagDelete: (tagIds: string[]) => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||
const [showCreateTagInput, setShowCreateTagInput] = useState(false);
|
||||
@@ -74,10 +71,9 @@ export const VirtualizedTagList = ({
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
onTagDelete(selectedTagIds);
|
||||
toast(t['com.affine.delete-tags.count']({ count: selectedTagIds.length }));
|
||||
hideFloatingToolbar();
|
||||
return;
|
||||
}, [hideFloatingToolbar, onTagDelete, selectedTagIds, t]);
|
||||
}, [hideFloatingToolbar, onTagDelete, selectedTagIds]);
|
||||
|
||||
const onOpenCreate = useCallback(() => {
|
||||
setShowCreateTagInput(true);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { Tag } from './entities/tag';
|
||||
export { tagColorMap } from './entities/utils';
|
||||
export { TagService } from './service/tag';
|
||||
export { DeleteTagConfirmModal } from './view/delete-tag-modal';
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { ConfirmModal, toast } from '@affine/component';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { TagService } from '../service/tag';
|
||||
|
||||
export const DeleteTagConfirmModal = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
selectedTagIds,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
selectedTagIds: string[];
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const tagService = useService(TagService);
|
||||
const tags = useLiveData(tagService.tags);
|
||||
const selectedTags = useMemo(() => {
|
||||
return tags.filter(tag => selectedTagIds.includes(tag.id));
|
||||
}, [selectedTagIds, tags]);
|
||||
const tagName = useLiveData(selectedTags[0]?.value || '');
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
selectedTagIds.forEach(tagId => {
|
||||
tagService.deleteTag(tagId);
|
||||
});
|
||||
|
||||
toast(
|
||||
selectedTagIds.length > 1
|
||||
? t['com.affine.delete-tags.count']({ count: selectedTagIds.length })
|
||||
: t['com.affine.tags.delete-tags.toast']()
|
||||
);
|
||||
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange, selectedTagIds, t, tagService]);
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t['com.affine.delete-tags.confirm.title']()}
|
||||
description={
|
||||
selectedTags.length === 1 ? (
|
||||
<Trans
|
||||
i18nKey={'com.affine.delete-tags.confirm.description'}
|
||||
values={{ tag: tagName }}
|
||||
components={{ 1: <strong /> }}
|
||||
/>
|
||||
) : (
|
||||
t['com.affine.delete-tags.confirm.multi-tag-description']({
|
||||
count: selectedTags.length.toString(),
|
||||
})
|
||||
)
|
||||
}
|
||||
confirmButtonOptions={{
|
||||
type: 'warning',
|
||||
children: t['Delete'](),
|
||||
}}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
} from '@affine/core/components/page-list/tags';
|
||||
import { CreateOrEditTag } from '@affine/core/components/page-list/tags/create-tag';
|
||||
import type { TagMeta } from '@affine/core/components/page-list/types';
|
||||
import { TagService } from '@affine/core/modules/tag';
|
||||
import { DeleteTagConfirmModal, TagService } from '@affine/core/modules/tag';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
@@ -33,16 +33,25 @@ const EmptyTagListHeader = () => {
|
||||
export const AllTag = () => {
|
||||
const tagService = useService(TagService);
|
||||
const tags = useLiveData(tagService.tags);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
|
||||
|
||||
const tagMetas: TagMeta[] = useLiveData(tagService.tagMetas);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(tagIds: string[]) => {
|
||||
tagIds.forEach(tagId => {
|
||||
tagService.deleteTag(tagId);
|
||||
});
|
||||
const handleCloseModal = useCallback(
|
||||
(open: boolean) => {
|
||||
setOpen(open);
|
||||
setSelectedTagIds([]);
|
||||
},
|
||||
[tagService]
|
||||
[setOpen]
|
||||
);
|
||||
|
||||
const onTagDelete = useCallback(
|
||||
(tagIds: string[]) => {
|
||||
setOpen(true);
|
||||
setSelectedTagIds(tagIds);
|
||||
},
|
||||
[setOpen, setSelectedTagIds]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -56,13 +65,18 @@ export const AllTag = () => {
|
||||
<VirtualizedTagList
|
||||
tags={tags}
|
||||
tagMetas={tagMetas}
|
||||
onTagDelete={handleDelete}
|
||||
onTagDelete={onTagDelete}
|
||||
/>
|
||||
) : (
|
||||
<EmptyTagList heading={<EmptyTagListHeader />} />
|
||||
)}
|
||||
</div>
|
||||
</ViewBodyIsland>
|
||||
<DeleteTagConfirmModal
|
||||
open={open}
|
||||
onOpenChange={handleCloseModal}
|
||||
selectedTagIds={selectedTagIds}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1165,6 +1165,9 @@
|
||||
"com.affine.delete-tags.count": "{{count}} tag deleted",
|
||||
"com.affine.delete-tags.count_one": "{{count}} tag deleted",
|
||||
"com.affine.delete-tags.count_other": "{{count}} tags deleted",
|
||||
"com.affine.delete-tags.confirm.title": "Delete Tag?",
|
||||
"com.affine.delete-tags.confirm.description": "Deleting <1>{{tag}}</1> cannot be undone, please proceed with caution.",
|
||||
"com.affine.delete-tags.confirm.multi-tag-description": "Deleting {{count}} tags cannot be undone, please proceed with caution.",
|
||||
"com.affine.workbench.split-view-menu.keep-this-one": "Solo View",
|
||||
"com.affine.workbench.split-view.page-menu-open": "Open in split view",
|
||||
"com.affine.search-tags.placeholder": "Type here ...",
|
||||
|
||||
Reference in New Issue
Block a user