Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/components/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,19 @@ export function AppSidebar({

const homeDir = useHomeDir();
const activeSession = sessions.find((s) => s.id === activeSessionId);
const activeSessionDirectory =
const executionDirectory =
getSessionExecutionDirectory(activeSession) || activeTargetDirectory || null;
const activeSessionSidebarMeta = activeSession ? sessionMeta[activeSession.id] : undefined;
const activeSessionIsChat = activeSession
? activeSessionSidebarMeta?.sidebarSection === "chats" ||
(!activeSessionSidebarMeta?.sidebarSection &&
normalizeProjectPath(getSessionExecutionDirectory(activeSession) ?? "") ===
normalizeProjectPath(defaultChatDirectory ?? ""))
: normalizeProjectPath(activeTargetDirectory ?? "") ===
normalizeProjectPath(defaultChatDirectory ?? "");
const activeSessionDirectory = activeSessionIsChat
? null
: activeSessionSidebarMeta?.displayProjectDir || executionDirectory;

const {
matchingSessionIds: messageMatchingSessionIds,
Expand Down
66 changes: 46 additions & 20 deletions src/components/sidebar/ProjectEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import {
ChevronDown,
ChevronRight,
ChevronUp,
Folder,
FolderOpen,
GripVertical,
Plus,
SquarePen,
} from "lucide-react";
import * as ContextMenu from "@/components/ui/context-menu";
Expand Down Expand Up @@ -122,14 +124,14 @@ export function ProjectEntry({
};

return (
<div key={directory} className="mb-1">
<div key={directory} className="group/project-block mb-1.5">
<SidebarMenu>
<ContextMenu.Root>
<ContextMenu.Trigger asChild>
<SidebarMenuItem className="overflow-visible">
<SidebarRow
label={getProjectName(directory)}
className="group/project font-medium"
className="group/project relative font-medium"
onActivate={(event) => {
if (sidebarState === "collapsed") {
const top = event.currentTarget.getBoundingClientRect().top;
Expand All @@ -141,17 +143,32 @@ export function ProjectEntry({
toggleCollapsed(directory);
}}
leadingAction={
canDrag ? (
<button
{...(dragHandleProps ?? {})}
type="button"
data-project-action
data-project-drag-handle
className="flex size-6 shrink-0 cursor-grab items-center justify-center rounded text-muted-foreground/70 hover:bg-accent hover:text-foreground active:cursor-grabbing group-data-[collapsible=icon]:hidden"
aria-label={`Reorder ${getProjectName(directory)}`}
>
<GripVertical className="size-3.5" />
</button>
sidebarState !== "collapsed" ? (
<span className="relative flex size-5 shrink-0 items-center justify-center group-data-[collapsible=icon]:hidden">
<button
type="button"
data-project-action
className={`flex size-5 items-center justify-center rounded text-muted-foreground transition-opacity hover:bg-accent hover:text-foreground ${canDrag ? "group-hover/project-block:opacity-0 group-focus-within/project-block:opacity-0" : ""}`}
aria-label={getProjectName(directory)}
onClick={() => toggleCollapsed(directory)}
>
<ChevronRight
className={`size-3.5 transition-transform ${!isCollapsed ? "rotate-90" : ""}`}
/>
</button>
{canDrag && (
<button
{...(dragHandleProps ?? {})}
type="button"
data-project-action
data-project-drag-handle
className="pointer-events-none absolute inset-0 flex size-5 cursor-grab items-center justify-center rounded text-muted-foreground/60 opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover/project-block:pointer-events-auto group-hover/project-block:opacity-100 group-focus-within/project-block:pointer-events-auto group-focus-within/project-block:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100 active:cursor-grabbing"
aria-label={`Reorder ${getProjectName(directory)}`}
>
<GripVertical className="size-3.5" />
</button>
)}
</span>
) : undefined
}
actions={
Expand Down Expand Up @@ -181,10 +198,10 @@ export function ProjectEntry({
<Spinner className="shrink-0 size-4 text-muted-foreground" />
) : sidebarState === "collapsed" ? (
<FolderOpen className="shrink-0 size-4" />
) : isCollapsed ? (
<Folder className="shrink-0 size-4 text-muted-foreground" />
) : (
<ChevronRight
className={`shrink-0 size-4 transition-transform ${!isCollapsed ? "rotate-90" : ""}`}
/>
<FolderOpen className="shrink-0 size-4 text-muted-foreground" />
)}
<span className="truncate min-w-0 flex-1" data-responsive-allow="text-clip">
{getProjectName(directory)}
Expand All @@ -204,11 +221,20 @@ export function ProjectEntry({
</ContextMenu.Root>
</SidebarMenu>
{!isCollapsed && sidebarState !== "collapsed" && (
<SidebarMenu className="ml-3 border-l border-sidebar-border pl-2 w-[calc(100%-0.75rem)] overflow-x-hidden">
<SidebarMenu className="ml-3 w-[calc(100%-0.75rem)] gap-0.5 overflow-x-hidden pl-2">
{dirSessions.length === 0 ? (
<div className="px-2 py-1 text-[11px] text-muted-foreground">
{t("sidebar.noSessionsYet")}
</div>
<button
type="button"
aria-label={t("sidebar.newSession")}
className="flex min-h-8 w-full items-center gap-2 rounded-md px-2 text-left text-xs text-muted-foreground transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring"
onClick={() => {
setActiveTarget(directory, { newChat: true });
closeMobileSidebar();
}}
>
<Plus className="size-3.5 shrink-0" />
<span className="truncate">{t("projectMenu.newSession")}</span>
</button>
) : (
<>
{visibleSessions.map((session) =>
Expand Down
11 changes: 7 additions & 4 deletions src/components/sidebar/SessionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ export function SessionRow({
const hasPermission = !!pendingPermissions[session.id];
const meta = sessionMeta[session.id];
const hasColor = !!meta?.color;
const colorBorderClass = hasColor
? `border-l-[3px] -ml-[3px] ${getColorBorderClass(meta.color)}`
: "";
const colorDotClass = hasColor ? getColorBorderClass(meta.color).replace("border-", "bg-") : "";
const tags = meta?.tags ?? [];
const isPinned = !!meta?.pinnedAt;
const isNaming = namingSessionIds.has(session.id);
Expand Down Expand Up @@ -178,7 +176,7 @@ export function SessionRow({
label={displayTitle}
active={isActive}
editing={editingSessionId === session.id}
className={`group/session ${colorBorderClass}`}
className={`group/session text-muted-foreground ${isActive ? "text-sidebar-accent-foreground" : ""}`}
onActivate={() => {
void selectSession(session.id);
closeMobileSidebar();
Expand Down Expand Up @@ -217,6 +215,11 @@ export function SessionRow({
{hasUnsent && (
<span className="absolute -bottom-0.5 -right-0.5 size-2 rounded-full bg-amber-500 ring-1 ring-sidebar" />
)}
{hasColor && (
<span
className={`absolute -left-1 top-1/2 size-1.5 -translate-y-1/2 rounded-full ring-1 ring-sidebar ${colorDotClass}`}
/>
)}
</span>
{editingSessionId === session.id ? (
<input
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
"clearSearch": "Sidebar-Suche leeren",
"loadMore": "Mehr laden ({{count}})",
"newChat": "Neuer Chat",
"newSession": "Neue Sitzung",
"newSession": "Neue Session",
"noChats": "Keine Chats",
"noMatches": "Keine Treffer für \"{{query}}\"",
"noSessionsYet": "Noch keine Sitzungen",
Expand Down
Loading