diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index aa6e0f4..ebe3df6 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -66,6 +66,7 @@ export function Sidebar() { null, ); const [editingFolderId, setEditingFolderId] = useState(null); + const [creatingFolderId, setCreatingFolderId] = useState(null); const [creatingFeedId, setCreatingFeedId] = useState(null); const [editingFeedId, setEditingFeedId] = useState(null); const [activeDragId, setActiveDragId] = useState(null); @@ -169,9 +170,28 @@ export function Sidebar() { void persistSidebarStructure(); } - async function handleCreateFolder() { + async function handleCreateFolder(parentFolderId: string | null = null) { const folder = await createRootFolder(); + setCreatingFolderId(folder.id); + + if (!parentFolderId) { + setEditingFolderId(folder.id); + return; + } + + const nextFolders = [ + ...folders, + { + ...folder, + parentFolderId, + sortOrder: folders.filter((item) => item.parentFolderId === parentFolderId).length, + }, + ]; + + setSidebarStructure(nextFolders, feeds); + setFolderExpanded(parentFolderId, true); setEditingFolderId(folder.id); + await persistSidebarStructure(); } async function handleCreateFeed(folderId: string | null = null) { @@ -246,12 +266,20 @@ export function Sidebar() { async function handleCommitFolderRename(folderId: string, name: string) { try { await renameFolder(folderId, name); + setCreatingFolderId(null); setEditingFolderId(null); } catch { + setCreatingFolderId(null); setEditingFolderId(null); } } + async function handleCancelCreateFolder(folderId: string) { + setCreatingFolderId(null); + setEditingFolderId(null); + await removeFolder(folderId); + } + async function handleDeleteFolder(folderId: string) { const deletedFolderIds = [folderId, ...getDescendantFolderIds(folders, folderId)]; const deletedFeedIds = feeds @@ -406,13 +434,16 @@ export function Sidebar() { } onSelectFeed={(feedId) => setCurrentRoute(ROUTE.FEED, { feedId })} onCreateFeed={handleCreateFeed} + onCreateFolder={handleCreateFolder} editingFolderId={editingFolderId} + creatingFolderId={creatingFolderId} creatingFeedId={creatingFeedId} editingFeedId={editingFeedId} pendingDeleteFolderId={pendingDeleteFolderId} onCommitFolderRename={handleCommitFolderRename} onCommitFeedRename={handleCommitFeedRename} onCancelFolderRename={() => setEditingFolderId(null)} + onCancelCreateFolder={handleCancelCreateFolder} onCancelFeedRename={() => setEditingFeedId(null)} onStartFolderRename={(folderId) => setEditingFolderId(folderId)} onStartFeedRename={(feedId) => setEditingFeedId(feedId)} @@ -468,13 +499,16 @@ function SidebarTreeContext({ onSelectFolder, onSelectFeed, onCreateFeed, + onCreateFolder, editingFolderId, + creatingFolderId, creatingFeedId, editingFeedId, pendingDeleteFolderId, onCommitFolderRename, onCommitFeedRename, onCancelFolderRename, + onCancelCreateFolder, onCancelFeedRename, onStartFolderRename, onStartFeedRename, @@ -499,7 +533,9 @@ function SidebarTreeContext({ onSelectFolder: (folderId: string) => void; onSelectFeed: (feedId: string) => void; onCreateFeed: (folderId?: string | null) => void | Promise; + onCreateFolder: (parentFolderId?: string | null) => void | Promise; editingFolderId: string | null; + creatingFolderId: string | null; creatingFeedId: string | null; editingFeedId: string | null; pendingDeleteFolderId: string | null; @@ -509,6 +545,7 @@ function SidebarTreeContext({ ) => void | Promise; onCommitFeedRename: (feedId: string, title: string) => void | Promise; onCancelFolderRename: () => void; + onCancelCreateFolder: (folderId: string) => void | Promise; onCancelFeedRename: () => void; onStartFolderRename: (folderId: string) => void; onStartFeedRename: (feedId: string) => void; @@ -600,6 +637,7 @@ function SidebarTreeContext({ isActive={isActive} isDropTarget={activeDropFolderId === node.id} dropInsideZoneId={buildFolderRowDropZoneId(node.id)} + isCreating={creatingFolderId === node.id} isEditing={editingFolderId === node.id} onToggle={() => onToggleFolder(node.id)} onClick={(event) => { @@ -615,10 +653,12 @@ function SidebarTreeContext({ onSelectFolder(node.id); }} onContextMenuCreateFeed={() => void onCreateFeed(node.id)} + onContextMenuCreateFolder={() => void onCreateFolder(node.id)} onContextMenuRename={() => onStartFolderRename(node.id)} onContextMenuDelete={() => onRequestDeleteFolder(node.id)} onCommitRename={(name) => onCommitFolderRename(node.id, name)} onCancelRename={onCancelFolderRename} + onCancelCreate={() => onCancelCreateFolder(node.id)} deleteConfirmationMessage={deleteConfirmationMessage} onCancelDelete={onCancelDeleteFolder} onConfirmDelete={() => void onConfirmDeleteFolder(node.id)} @@ -639,13 +679,16 @@ function SidebarTreeContext({ onSelectFolder={onSelectFolder} onSelectFeed={onSelectFeed} onCreateFeed={onCreateFeed} + onCreateFolder={onCreateFolder} editingFolderId={editingFolderId} + creatingFolderId={creatingFolderId} creatingFeedId={creatingFeedId} editingFeedId={editingFeedId} pendingDeleteFolderId={pendingDeleteFolderId} onCommitFolderRename={onCommitFolderRename} onCommitFeedRename={onCommitFeedRename} onCancelFolderRename={onCancelFolderRename} + onCancelCreateFolder={onCancelCreateFolder} onCancelFeedRename={onCancelFeedRename} onStartFolderRename={onStartFolderRename} onStartFeedRename={onStartFeedRename} diff --git a/src/components/SidebarFolderItem.tsx b/src/components/SidebarFolderItem.tsx index 5c4753f..48c5b7e 100644 --- a/src/components/SidebarFolderItem.tsx +++ b/src/components/SidebarFolderItem.tsx @@ -19,14 +19,17 @@ type SidebarFolderItemProps = { isActive?: boolean; isDropTarget?: boolean; dropInsideZoneId?: string; + isCreating?: boolean; isEditing?: boolean; onClick?: (event: React.MouseEvent) => void; + onContextMenuCreateFolder?: () => void; onContextMenuCreateFeed?: () => void; onContextMenuRename?: () => void; onContextMenuDelete?: () => void; onToggle?: () => void; onCommitRename?: (name: string) => void | Promise; onCancelRename?: () => void; + onCancelCreate?: () => void | Promise; deleteConfirmationMessage?: string | null; onCancelDelete?: () => void; onConfirmDelete?: () => void; @@ -40,14 +43,17 @@ export function SidebarFolderItem({ isActive, isDropTarget, dropInsideZoneId, + isCreating, isEditing, onClick, + onContextMenuCreateFolder, onContextMenuCreateFeed, onContextMenuRename, onContextMenuDelete, onToggle, onCommitRename, onCancelRename, + onCancelCreate, deleteConfirmationMessage, onCancelDelete, onConfirmDelete, @@ -65,19 +71,24 @@ export function SidebarFolderItem({ }, [label]); useEffect(() => { - if (!isEditing) { + if (!isCreating && !isEditing) { return; } inputRef.current?.focus(); inputRef.current?.select(); - }, [isEditing]); + }, [isCreating, isEditing]); function handleCommit() { void onCommitRename?.(draftName); } const contextMenuItems = [ + { + id: `${id}:new-folder`, + text: "New folder", + onSelect: onContextMenuCreateFolder, + }, { id: `${id}:new-feed`, text: "New feed", @@ -112,12 +123,12 @@ export function SidebarFolderItem({ : "", )} style={{ paddingLeft: 10 + depth * 13 }} - {...(isEditing ? {} : attributes)} - {...(isEditing ? {} : listeners)} + {...(isCreating || isEditing ? {} : attributes)} + {...(isCreating || isEditing ? {} : listeners)} onClick={onClick} onDoubleClick={onToggle} onContextMenu={ - isEditing + isCreating || isEditing ? undefined : (event) => { void showNativeContextMenu(event, contextMenuItems); @@ -148,7 +159,7 @@ export function SidebarFolderItem({ )} - {isEditing ? ( + {isCreating || isEditing ? (