Use file search when leaving a review comment (vibe-kanban) (#743)
* Perfect! I've successfully implemented file search functionality for review comments. Here's a summary of what was completed:
## ✅ Implementation Complete
### **Changes Made:**
1. **Updated [`ReviewCommentRenderer.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-a558-use-file-s/frontend/src/components/diff/ReviewCommentRenderer.tsx)**:
- Replaced basic textarea with FileSearchTextarea in edit mode
- Added `projectId` prop to interface
- Imported and configured FileSearchTextarea with proper styling
2. **Updated [`CommentWidgetLine.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-a558-use-file-s/frontend/src/components/diff/CommentWidgetLine.tsx)**:
- Replaced textarea with FileSearchTextarea for new comment creation
- Added `projectId` prop to interface
3. **Updated [`DiffCard.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-a558-use-file-s/frontend/src/components/DiffCard.tsx)**:
- Integrated useProject hook to get projectId from context
- Passed projectId to both ReviewCommentRenderer and CommentWidgetLine
4. **Enhanced [`ReviewProvider.tsx`](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/vk-a558-use-file-s/frontend/src/contexts/ReviewProvider.tsx)**:
- Added regex pattern to format file paths with backticks in generated markdown
- Preserves existing functionality while enhancing visual formatting
### **Key Features:**
- **@-mention file search** in both new and edit review comments
- **Auto-complete dropdown** with arrow key navigation
- **Project context integration** - no schema changes needed
- **Enhanced markdown export** with properly formatted file paths
- **Preserves all existing functionality** - keyboard shortcuts, styling, etc.
### **User Experience:**
1. Type `@` in any review comment field
2. Search dropdown appears automatically
3. Arrow keys ↑↓ to navigate, Enter to select, Esc to dismiss
4. Selected files inserted as full paths
5. Generated review markdown formats paths with backticks for readability
The implementation successfully integrates file search into the review comment workflow while maintaining the existing architecture and user experience!
* Cleanup script changes for task attempt a558e290-557a-475f-81e9-be9ae44a3621
* Perfect! Updated both placeholder texts to inform users about the file search functionality:
- **New comments**: `"Add a comment... (type @ to search files)"`
- **Edit comments**: `"Edit comment... (type @ to search files)"`
This provides clear user guidance about the @ file search feature right in the input fields.
This commit is contained in:
committed by
GitHub
parent
40df3d17fe
commit
f4a82aebd2
@@ -26,6 +26,7 @@ import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
|
|||||||
import { CommentWidgetLine } from '@/components/diff/CommentWidgetLine';
|
import { CommentWidgetLine } from '@/components/diff/CommentWidgetLine';
|
||||||
import { ReviewCommentRenderer } from '@/components/diff/ReviewCommentRenderer';
|
import { ReviewCommentRenderer } from '@/components/diff/ReviewCommentRenderer';
|
||||||
import { useDiffViewMode } from '@/stores/useDiffViewStore';
|
import { useDiffViewMode } from '@/stores/useDiffViewStore';
|
||||||
|
import { useProject } from '@/contexts/project-context';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
diff: Diff;
|
diff: Diff;
|
||||||
@@ -75,6 +76,7 @@ export default function DiffCard({
|
|||||||
const theme = getActualTheme(config?.theme);
|
const theme = getActualTheme(config?.theme);
|
||||||
const { comments, drafts, setDraft } = useReview();
|
const { comments, drafts, setDraft } = useReview();
|
||||||
const globalMode = useDiffViewMode();
|
const globalMode = useDiffViewMode();
|
||||||
|
const { projectId } = useProject();
|
||||||
|
|
||||||
const oldName = diff.oldPath || undefined;
|
const oldName = diff.oldPath || undefined;
|
||||||
const newName = diff.newPath || oldName || 'unknown';
|
const newName = diff.newPath || oldName || 'unknown';
|
||||||
@@ -173,12 +175,15 @@ export default function DiffCard({
|
|||||||
widgetKey={widgetKey}
|
widgetKey={widgetKey}
|
||||||
onSave={props.onClose}
|
onSave={props.onClose}
|
||||||
onCancel={props.onClose}
|
onCancel={props.onClose}
|
||||||
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderExtendLine = (lineData: any) => {
|
const renderExtendLine = (lineData: any) => {
|
||||||
return <ReviewCommentRenderer comment={lineData.data} />;
|
return (
|
||||||
|
<ReviewCommentRenderer comment={lineData.data} projectId={projectId} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Title row
|
// Title row
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { FileSearchTextarea } from '@/components/ui/file-search-textarea';
|
||||||
import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
|
import { useReview, type ReviewDraft } from '@/contexts/ReviewProvider';
|
||||||
|
|
||||||
interface CommentWidgetLineProps {
|
interface CommentWidgetLineProps {
|
||||||
@@ -7,6 +8,7 @@ interface CommentWidgetLineProps {
|
|||||||
widgetKey: string;
|
widgetKey: string;
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
projectId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommentWidgetLine({
|
export function CommentWidgetLine({
|
||||||
@@ -14,6 +16,7 @@ export function CommentWidgetLine({
|
|||||||
widgetKey,
|
widgetKey,
|
||||||
onSave,
|
onSave,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
projectId,
|
||||||
}: CommentWidgetLineProps) {
|
}: CommentWidgetLineProps) {
|
||||||
const { setDraft, addComment } = useReview();
|
const { setDraft, addComment } = useReview();
|
||||||
const [value, setValue] = useState(draft.text);
|
const [value, setValue] = useState(draft.text);
|
||||||
@@ -52,14 +55,15 @@ export function CommentWidgetLine({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 border-y">
|
<div className="p-4 border-y">
|
||||||
<textarea
|
<FileSearchTextarea
|
||||||
ref={textareaRef}
|
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={setValue}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Add a comment..."
|
placeholder="Add a comment... (type @ to search files)"
|
||||||
className="w-full bg-primary text-primary-foreground text-sm font-mono resize-none min-h-[60px] focus:outline-none focus:ring-1 focus:ring-primary"
|
|
||||||
rows={3}
|
rows={3}
|
||||||
|
maxRows={10}
|
||||||
|
className="w-full bg-primary text-primary-foreground text-sm font-mono resize-none min-h-[60px] focus:outline-none focus:ring-1 focus:ring-primary"
|
||||||
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
<div className="mt-2 flex gap-2">
|
<div className="mt-2 flex gap-2">
|
||||||
<Button size="xs" onClick={handleSave} disabled={!value.trim()}>
|
<Button size="xs" onClick={handleSave} disabled={!value.trim()}>
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { Trash2, Pencil } from 'lucide-react';
|
import { Trash2, Pencil } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { FileSearchTextarea } from '@/components/ui/file-search-textarea';
|
||||||
import { useReview, type ReviewComment } from '@/contexts/ReviewProvider';
|
import { useReview, type ReviewComment } from '@/contexts/ReviewProvider';
|
||||||
|
|
||||||
interface ReviewCommentRendererProps {
|
interface ReviewCommentRendererProps {
|
||||||
comment: ReviewComment;
|
comment: ReviewComment;
|
||||||
|
projectId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReviewCommentRenderer({ comment }: ReviewCommentRendererProps) {
|
export function ReviewCommentRenderer({
|
||||||
|
comment,
|
||||||
|
projectId,
|
||||||
|
}: ReviewCommentRendererProps) {
|
||||||
const { deleteComment, updateComment } = useReview();
|
const { deleteComment, updateComment } = useReview();
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [editText, setEditText] = useState(comment.text);
|
const [editText, setEditText] = useState(comment.text);
|
||||||
@@ -51,14 +56,15 @@ export function ReviewCommentRenderer({ comment }: ReviewCommentRendererProps) {
|
|||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<div className="border-y bg-background p-4">
|
<div className="border-y bg-background p-4">
|
||||||
<textarea
|
<FileSearchTextarea
|
||||||
ref={textareaRef}
|
|
||||||
value={editText}
|
value={editText}
|
||||||
onChange={(e) => setEditText(e.target.value)}
|
onChange={setEditText}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Edit comment..."
|
placeholder="Edit comment... (type @ to search files)"
|
||||||
className="w-full bg-background text-foreground text-sm font-mono resize-none min-h-[60px] focus:outline-none"
|
|
||||||
rows={3}
|
rows={3}
|
||||||
|
maxRows={10}
|
||||||
|
className="w-full bg-background text-foreground text-sm font-mono resize-none min-h-[60px] focus:outline-none"
|
||||||
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
<div className="mt-2 flex gap-2">
|
<div className="mt-2 flex gap-2">
|
||||||
<Button size="xs" onClick={handleSave} disabled={!editText.trim()}>
|
<Button size="xs" onClick={handleSave} disabled={!editText.trim()}>
|
||||||
|
|||||||
@@ -96,11 +96,14 @@ export function ReviewProvider({ children }: { children: ReactNode }) {
|
|||||||
const commentsMd = comments
|
const commentsMd = comments
|
||||||
.map((comment) => {
|
.map((comment) => {
|
||||||
const codeLine = formatCodeLine(comment.codeLine);
|
const codeLine = formatCodeLine(comment.codeLine);
|
||||||
const commentBody = comment.text.trim();
|
// Format file paths in comment body with backticks
|
||||||
|
const bodyWithFormattedPaths = comment.text
|
||||||
|
.trim()
|
||||||
|
.replace(/([/\\]?[\w.-]+(?:[/\\][\w.-]+)+)/g, '`$1`');
|
||||||
if (codeLine) {
|
if (codeLine) {
|
||||||
return `**${comment.filePath}** (Line ${comment.lineNumber})\n${codeLine}\n\n> ${commentBody}\n`;
|
return `**${comment.filePath}** (Line ${comment.lineNumber})\n${codeLine}\n\n> ${bodyWithFormattedPaths}\n`;
|
||||||
}
|
}
|
||||||
return `**${comment.filePath}** (Line ${comment.lineNumber})\n\n> ${commentBody}\n`;
|
return `**${comment.filePath}** (Line ${comment.lineNumber})\n\n> ${bodyWithFormattedPaths}\n`;
|
||||||
})
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user