mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-10 21:48:48 +08:00
fix(core): confirm the tag name before creating a new tag (#11724)
close AF-1569 - Show rename modal below the "Add Tag" button instead of at the new tag node - Tag is created only after the user confirms the name in the modal - Improves sidebar tag creation flow and user experience 
This commit is contained in:
@@ -28,10 +28,8 @@ export const ExplorerTagNode = ({
|
|||||||
operations: additionalOperations,
|
operations: additionalOperations,
|
||||||
dropEffect,
|
dropEffect,
|
||||||
canDrop,
|
canDrop,
|
||||||
defaultRenaming,
|
|
||||||
}: {
|
}: {
|
||||||
tagId: string;
|
tagId: string;
|
||||||
defaultRenaming?: boolean;
|
|
||||||
} & GenericExplorerNode) => {
|
} & GenericExplorerNode) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { tagService, globalContextService } = useServices({
|
const { tagService, globalContextService } = useServices({
|
||||||
@@ -179,7 +177,6 @@ export const ExplorerTagNode = ({
|
|||||||
setCollapsed={setCollapsed}
|
setCollapsed={setCollapsed}
|
||||||
to={`/tag/${tagId}`}
|
to={`/tag/${tagId}`}
|
||||||
active={active}
|
active={active}
|
||||||
defaultRenaming={defaultRenaming}
|
|
||||||
reorderable={reorderable}
|
reorderable={reorderable}
|
||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
canDrop={handleCanDrop}
|
canDrop={handleCanDrop}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
|
||||||
import type { Tag } from '@affine/core/modules/tag';
|
|
||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
@@ -11,6 +10,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { ExplorerService } from '../../../services/explorer';
|
import { ExplorerService } from '../../../services/explorer';
|
||||||
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
import { CollapsibleSection } from '../../layouts/collapsible-section';
|
||||||
import { ExplorerTagNode } from '../../nodes/tag';
|
import { ExplorerTagNode } from '../../nodes/tag';
|
||||||
|
import { ExplorerTreeNodeRenameModal as CreateTagModal } from '../../tree/node';
|
||||||
import { RootEmpty } from './empty';
|
import { RootEmpty } from './empty';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
@@ -21,25 +21,28 @@ export const ExplorerTags = () => {
|
|||||||
});
|
});
|
||||||
const explorerSection = explorerService.sections.tags;
|
const explorerSection = explorerService.sections.tags;
|
||||||
const collapsed = useLiveData(explorerSection.collapsed$);
|
const collapsed = useLiveData(explorerSection.collapsed$);
|
||||||
const [createdTag, setCreatedTag] = useState<Tag | null>(null);
|
const [creating, setCreating] = useState(false);
|
||||||
const tags = useLiveData(tagService.tagList.tags$);
|
const tags = useLiveData(tagService.tagList.tags$);
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const handleCreateNewFavoriteDoc = useCallback(() => {
|
const handleCreateNewTag = useCallback(
|
||||||
const newTags = tagService.tagList.createTag(
|
(name: string) => {
|
||||||
t['com.affine.rootAppSidebar.tags.new-tag'](),
|
tagService.tagList.createTag(name, tagService.randomTagColor());
|
||||||
tagService.randomTagColor()
|
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
||||||
);
|
explorerSection.setCollapsed(false);
|
||||||
setCreatedTag(newTags);
|
},
|
||||||
track.$.navigationPanel.organize.createOrganizeItem({ type: 'tag' });
|
[explorerSection, tagService]
|
||||||
explorerSection.setCollapsed(false);
|
);
|
||||||
}, [explorerSection, t, tagService]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (collapsed) setCreatedTag(null); // reset created tag to clear the renaming state
|
if (collapsed) setCreating(false);
|
||||||
}, [collapsed]);
|
}, [collapsed]);
|
||||||
|
|
||||||
|
const handleOpenCreateModal = useCallback(() => {
|
||||||
|
setCreating(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
name="tags"
|
name="tags"
|
||||||
@@ -47,16 +50,26 @@ export const ExplorerTags = () => {
|
|||||||
headerClassName={styles.draggedOverHighlight}
|
headerClassName={styles.draggedOverHighlight}
|
||||||
title={t['com.affine.rootAppSidebar.tags']()}
|
title={t['com.affine.rootAppSidebar.tags']()}
|
||||||
actions={
|
actions={
|
||||||
<IconButton
|
<div className={styles.iconContainer}>
|
||||||
data-testid="explorer-bar-add-favorite-button"
|
<IconButton
|
||||||
onClick={handleCreateNewFavoriteDoc}
|
data-testid="explorer-bar-add-tag-button"
|
||||||
size="16"
|
onClick={handleOpenCreateModal}
|
||||||
tooltip={t[
|
size="16"
|
||||||
'com.affine.rootAppSidebar.explorer.tag-section-add-tooltip'
|
tooltip={t[
|
||||||
]()}
|
'com.affine.rootAppSidebar.explorer.tag-section-add-tooltip'
|
||||||
>
|
]()}
|
||||||
<AddTagIcon />
|
>
|
||||||
</IconButton>
|
<AddTagIcon />
|
||||||
|
</IconButton>
|
||||||
|
{creating && (
|
||||||
|
<CreateTagModal
|
||||||
|
setRenaming={setCreating}
|
||||||
|
handleRename={handleCreateNewTag}
|
||||||
|
rawName={t['com.affine.rootAppSidebar.tags.new-tag']()}
|
||||||
|
className={styles.createModalAnchor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ExplorerTreeRoot placeholder={<RootEmpty />}>
|
<ExplorerTreeRoot placeholder={<RootEmpty />}>
|
||||||
@@ -68,7 +81,6 @@ export const ExplorerTags = () => {
|
|||||||
location={{
|
location={{
|
||||||
at: 'explorer:tags:list',
|
at: 'explorer:tags:list',
|
||||||
}}
|
}}
|
||||||
defaultRenaming={createdTag?.id === tag.id}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ExplorerTreeRoot>
|
</ExplorerTreeRoot>
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ export const draggedOverHighlight = style({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const iconContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const createModalAnchor = style({
|
||||||
|
top: 20,
|
||||||
|
left: 'auto',
|
||||||
|
right: 0,
|
||||||
|
transform: 'translateX(6px)',
|
||||||
|
});
|
||||||
|
|||||||
@@ -98,14 +98,16 @@ interface WebExplorerTreeNodeProps extends BaseExplorerTreeNodeProps {
|
|||||||
* specific rename modal for explorer tree node,
|
* specific rename modal for explorer tree node,
|
||||||
* Separate it into a separate component to prevent re-rendering the entire component when width changes.
|
* Separate it into a separate component to prevent re-rendering the entire component when width changes.
|
||||||
*/
|
*/
|
||||||
const ExplorerTreeNodeRenameModal = ({
|
export const ExplorerTreeNodeRenameModal = ({
|
||||||
setRenaming,
|
setRenaming,
|
||||||
handleRename,
|
handleRename,
|
||||||
rawName,
|
rawName,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
setRenaming: (renaming: boolean) => void;
|
setRenaming: (renaming: boolean) => void;
|
||||||
handleRename: (newName: string) => void;
|
handleRename: (newName: string) => void;
|
||||||
rawName: string | undefined;
|
rawName: string | undefined;
|
||||||
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const appSidebarService = useService(AppSidebarService).sidebar;
|
const appSidebarService = useService(AppSidebarService).sidebar;
|
||||||
const sidebarWidth = useLiveData(appSidebarService.width$);
|
const sidebarWidth = useLiveData(appSidebarService.width$);
|
||||||
@@ -117,7 +119,7 @@ const ExplorerTreeNodeRenameModal = ({
|
|||||||
onRename={handleRename}
|
onRename={handleRename}
|
||||||
currentName={rawName ?? ''}
|
currentName={rawName ?? ''}
|
||||||
>
|
>
|
||||||
<div className={styles.itemRenameAnchor} />
|
<div className={clsx(styles.itemRenameAnchor, className)} />
|
||||||
</RenameModal>
|
</RenameModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user