Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/types/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const historyItemSchema = z.object({
totalCost: z.number(),
size: z.number().optional(),
workspace: z.string().optional(),
starred: z.boolean().optional(),
})

export type HistoryItem = z.infer<typeof historyItemSchema>
11 changes: 11 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,17 @@ export const webviewMessageHandler = async (
case "deleteTaskWithId":
provider.deleteTaskWithId(message.text!)
break
case "toggleTaskStar":
if (message.text) {
const taskHistory = provider.getValue("taskHistory") || []
const taskIndex = taskHistory.findIndex((task) => task.id === message.text)
if (taskIndex !== -1) {
taskHistory[taskIndex].starred = !taskHistory[taskIndex].starred
await provider.setValue("taskHistory", taskHistory)
await provider.postStateToWebview()
}
}
break
case "deleteMultipleTasksWithIds": {
const ids = message.ids

Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface WebviewMessage {
| "deleteTaskWithId"
| "exportTaskWithId"
| "importSettings"
| "toggleTaskStar"
| "exportSettings"
| "resetState"
| "flushRouterModels"
Expand Down
66 changes: 52 additions & 14 deletions webview-ui/src/components/history/BatchDeleteTaskDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from "react"
import { useCallback, useMemo } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import {
AlertDialog,
Expand All @@ -13,6 +13,7 @@ import {
} from "@/components/ui"
import { vscode } from "@/utils/vscode"
import { AlertDialogProps } from "@radix-ui/react-alert-dialog"
import { useExtensionState } from "@/context/ExtensionStateContext"

interface BatchDeleteTaskDialogProps extends AlertDialogProps {
taskIds: string[]
Expand All @@ -21,36 +22,73 @@ interface BatchDeleteTaskDialogProps extends AlertDialogProps {
export const BatchDeleteTaskDialog = ({ taskIds, ...props }: BatchDeleteTaskDialogProps) => {
const { t } = useAppTranslation()
const { onOpenChange } = props
const { taskHistory } = useExtensionState()

// Filter out starred tasks
const { deletableTaskIds, starredCount } = useMemo(() => {
const deletable: string[] = []
let starred = 0

taskIds.forEach((id) => {
const task = taskHistory.find((item) => item.id === id)
if (task?.starred) {
starred++
} else {
deletable.push(id)
}
})

return { deletableTaskIds: deletable, starredCount: starred }
}, [taskIds, taskHistory])

const onDelete = useCallback(() => {
if (taskIds.length > 0) {
vscode.postMessage({ type: "deleteMultipleTasksWithIds", ids: taskIds })
if (deletableTaskIds.length > 0) {
vscode.postMessage({ type: "deleteMultipleTasksWithIds", ids: deletableTaskIds })
onOpenChange?.(false)
}
}, [taskIds, onOpenChange])
}, [deletableTaskIds, onOpenChange])

return (
<AlertDialog {...props}>
<AlertDialogContent className="max-w-md">
<AlertDialogHeader>
<AlertDialogTitle>{t("history:deleteTasks")}</AlertDialogTitle>
<AlertDialogDescription className="text-vscode-foreground">
<div className="mb-2">{t("history:confirmDeleteTasks", { count: taskIds.length })}</div>
<div className="text-vscode-editor-foreground bg-vscode-editor-background p-2 rounded text-sm">
{t("history:deleteTasksWarning")}
</div>
{starredCount > 0 ? (
<>
<div className="mb-2 text-vscode-notificationsWarningIcon-foreground">
{t("history:starredTasksExcluded", { count: starredCount })}
</div>
{deletableTaskIds.length > 0 && (
<div className="mb-2">
{t("history:confirmDeleteTasks", { count: deletableTaskIds.length })}
</div>
)}
</>
) : (
<div className="mb-2">
{t("history:confirmDeleteTasks", { count: deletableTaskIds.length })}
</div>
)}
{deletableTaskIds.length > 0 && (
<div className="text-vscode-editor-foreground bg-vscode-editor-background p-2 rounded text-sm">
{t("history:deleteTasksWarning")}
</div>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="secondary">{t("history:cancel")}</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDelete}>
<span className="codicon codicon-trash mr-1"></span>
{t("history:deleteItems", { count: taskIds.length })}
</Button>
</AlertDialogAction>
{deletableTaskIds.length > 0 && (
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDelete}>
<span className="codicon codicon-trash mr-1"></span>
{t("history:deleteItems", { count: deletableTaskIds.length })}
</Button>
</AlertDialogAction>
)}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down
30 changes: 21 additions & 9 deletions webview-ui/src/components/history/DeleteTaskDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Button,
} from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { useExtensionState } from "@/context/ExtensionStateContext"

import { vscode } from "@/utils/vscode"

Expand All @@ -24,9 +25,14 @@ interface DeleteTaskDialogProps extends AlertDialogProps {
export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) => {
const { t } = useAppTranslation()
const [isEnterPressed] = useKeyPress("Enter")
const { taskHistory } = useExtensionState()

const { onOpenChange } = props

// Check if the task is starred
const task = taskHistory.find((item) => item.id === taskId)
const isStarred = task?.starred || false

const onDelete = useCallback(() => {
if (taskId) {
vscode.postMessage({ type: "deleteTaskWithId", text: taskId })
Expand All @@ -35,27 +41,33 @@ export const DeleteTaskDialog = ({ taskId, ...props }: DeleteTaskDialogProps) =>
}, [taskId, onOpenChange])

useEffect(() => {
if (taskId && isEnterPressed) {
if (taskId && isEnterPressed && !isStarred) {
onDelete()
}
}, [taskId, isEnterPressed, onDelete])
}, [taskId, isEnterPressed, isStarred, onDelete])

return (
<AlertDialog {...props}>
<AlertDialogContent onEscapeKeyDown={() => onOpenChange?.(false)}>
<AlertDialogHeader>
<AlertDialogTitle>{t("history:deleteTask")}</AlertDialogTitle>
<AlertDialogDescription>{t("history:deleteTaskMessage")}</AlertDialogDescription>
<AlertDialogTitle>
{isStarred ? t("history:deleteStarredTask") : t("history:deleteTask")}
</AlertDialogTitle>
<AlertDialogDescription>
{isStarred ? t("history:deleteStarredTaskMessage") : t("history:deleteTaskMessage")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant="secondary">{t("history:cancel")}</Button>
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDelete}>
{t("history:delete")}
</Button>
</AlertDialogAction>
{!isStarred && (
<AlertDialogAction asChild>
<Button variant="destructive" onClick={onDelete}>
{t("history:delete")}
</Button>
</AlertDialogAction>
)}
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down
40 changes: 40 additions & 0 deletions webview-ui/src/components/history/StarButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react"
import { cn } from "@/lib/utils"
import { StandardTooltip } from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"

interface StarButtonProps {
itemId: string
isStarred?: boolean
onToggleStar: (itemId: string) => void
className?: string
}

export const StarButton: React.FC<StarButtonProps> = ({ itemId, isStarred, onToggleStar, className }) => {
const { t } = useAppTranslation()

const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
onToggleStar(itemId)
}

return (
<StandardTooltip content={isStarred ? t("history:unstarTask") : t("history:starTask")}>
<button
onClick={handleClick}
className={cn(
"p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors",
"focus:outline-none focus:ring-1 focus:ring-vscode-focusBorder",
className,
)}
aria-label={isStarred ? t("history:unstarTask") : t("history:starTask")}>
<span
className={cn("codicon", {
"codicon-star-full text-vscode-notificationsWarningIcon-foreground": isStarred,
"codicon-star-empty": !isStarred,
})}
/>
</button>
</StandardTooltip>
)
}
9 changes: 8 additions & 1 deletion webview-ui/src/components/history/TaskItemHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React from "react"
import type { HistoryItem } from "@roo-code/types"
import { formatDate } from "@/utils/format"
import { DeleteButton } from "./DeleteButton"
import { StarButton } from "./StarButton"
import { cn } from "@/lib/utils"
import { vscode } from "@/utils/vscode"

export interface TaskItemHeaderProps {
item: HistoryItem
Expand All @@ -11,12 +13,16 @@ export interface TaskItemHeaderProps {
}

const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, isSelectionMode, onDelete }) => {
const handleToggleStar = (itemId: string) => {
vscode.postMessage({ type: "toggleTaskStar", text: itemId })
}

return (
<div
className={cn("flex justify-between items-center", {
// this is to balance out the margin when we don't have a delete button
// because the delete button sorta pushes the date up due to its size
"mb-1": !onDelete,
"mb-1": !onDelete && !item.starred,
})}>
<div className="flex items-center flex-wrap gap-x-2 text-xs">
<span className="text-vscode-descriptionForeground font-medium text-sm uppercase">
Expand All @@ -27,6 +33,7 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, isSelectionMode,
{/* Action Buttons */}
{!isSelectionMode && (
<div className="flex flex-row gap-0 items-center opacity-20 group-hover:opacity-50 hover:opacity-100">
<StarButton itemId={item.id} isStarred={item.starred} onToggleStar={handleToggleStar} />
{onDelete && <DeleteButton itemId={item.id} onDelete={onDelete} />}
</div>
)}
Expand Down
5 changes: 5 additions & 0 deletions webview-ui/src/components/history/useTaskSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ export const useTaskSearch = () => {

// Then sort the results
return [...results].sort((a, b) => {
// Always sort starred items to the top
if (a.starred && !b.starred) return -1
if (!a.starred && b.starred) return 1

// If both are starred or both are not starred, apply the selected sort
switch (sortOption) {
case "oldest":
return (a.ts || 0) - (b.ts || 0)
Expand Down
5 changes: 5 additions & 0 deletions webview-ui/src/i18n/locales/en/history.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"exportTask": "Export Task",
"deleteTask": "Delete Task",
"deleteTaskMessage": "Are you sure you want to delete this task? This action cannot be undone.",
"deleteStarredTask": "Cannot Delete Starred Task",
"deleteStarredTaskMessage": "This task is starred and cannot be deleted. Please unstar it first if you want to delete it.",
"starTask": "Star task",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typographical note: The label "Star task" has inconsistent capitalization compared to similar entries (e.g. "Export Task", "Delete Task"). Consider changing it to "Star Task" for consistency.

Suggested change
"starTask": "Star task",
"starTask": "Star Task",

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

"unstarTask": "Unstar task",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typographical note: The label "Unstar task" has inconsistent capitalization compared to similar entries (e.g. "Export Task", "Delete Task"). Consider changing it to "Unstar Task" for consistency.

Suggested change
"unstarTask": "Unstar task",
"unstarTask": "Unstar Task",

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

"starredTasksExcluded": "{{count}} starred task(s) will not be deleted",
"cancel": "Cancel",
"delete": "Delete",
"exitSelection": "Exit Selection",
Expand Down
Loading