feat: copy-file autocomplete (#2004)
* add repo file search endpoint and use for copy-file autocomplete * address feedback and fix i18n errors * remove unused i18n
This commit is contained in:
committed by
GitHub
parent
5502a4cad6
commit
cdfb081cf8
@@ -1,31 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MultiFileSearchTextarea } from '@/components/ui/multi-file-search-textarea';
|
||||
|
||||
interface CopyFilesFieldProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
projectId: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function CopyFilesField({
|
||||
value,
|
||||
onChange,
|
||||
projectId,
|
||||
disabled = false,
|
||||
}: CopyFilesFieldProps) {
|
||||
const { t } = useTranslation('projects');
|
||||
|
||||
return (
|
||||
<MultiFileSearchTextarea
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={t('copyFilesPlaceholderWithSearch')}
|
||||
rows={3}
|
||||
disabled={disabled}
|
||||
className="w-full px-3 py-2 text-sm border border-input bg-background text-foreground disabled:opacity-50 rounded-md resize-vertical focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
projectId={projectId}
|
||||
maxRows={6}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -75,7 +75,8 @@ export function PreviewControls({
|
||||
)}
|
||||
onClick={() => onTabChange(process.id)}
|
||||
>
|
||||
{getDevServerWorkingDir(process) ?? 'Dev Server'}
|
||||
{getDevServerWorkingDir(process) ??
|
||||
t('preview.browser.devServerFallback')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
||||
import { usePortalContainer } from '@/contexts/PortalContainerContext';
|
||||
import { projectsApi } from '@/lib/api';
|
||||
import { projectsApi, repoApi } from '@/lib/api';
|
||||
|
||||
import type { SearchResult } from 'shared/types';
|
||||
|
||||
@@ -17,7 +17,10 @@ interface MultiFileSearchTextareaProps {
|
||||
rows?: number;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
projectId: string;
|
||||
/** Project ID for project-level file search (searches across all repos in project) */
|
||||
projectId?: string;
|
||||
/** Repo ID for repo-level file search (searches within a single repo) */
|
||||
repoId?: string;
|
||||
onKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
maxRows?: number;
|
||||
}
|
||||
@@ -30,9 +33,13 @@ export function MultiFileSearchTextarea({
|
||||
disabled = false,
|
||||
className,
|
||||
projectId,
|
||||
repoId,
|
||||
onKeyDown,
|
||||
maxRows = 10,
|
||||
}: MultiFileSearchTextareaProps) {
|
||||
// Require at least one of projectId or repoId
|
||||
const searchId = projectId || repoId;
|
||||
const searchType = projectId ? 'project' : 'repo';
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<FileSearchResult[]>([]);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
@@ -48,9 +55,16 @@ export function MultiFileSearchTextarea({
|
||||
const itemRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||
const portalContainer = usePortalContainer();
|
||||
|
||||
useEffect(() => {
|
||||
searchCacheRef.current.clear();
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
setShowDropdown(false);
|
||||
}, [searchId]);
|
||||
|
||||
// Search for files when query changes
|
||||
useEffect(() => {
|
||||
if (!searchQuery || !projectId || searchQuery.length < 2) {
|
||||
if (!searchQuery || !searchId || searchQuery.length < 2) {
|
||||
setSearchResults([]);
|
||||
setShowDropdown(false);
|
||||
return;
|
||||
@@ -77,14 +91,14 @@ export function MultiFileSearchTextarea({
|
||||
abortControllerRef.current = abortController;
|
||||
|
||||
try {
|
||||
const result = await projectsApi.searchFiles(
|
||||
projectId,
|
||||
searchQuery,
|
||||
'settings',
|
||||
{
|
||||
signal: abortController.signal,
|
||||
}
|
||||
);
|
||||
const result =
|
||||
searchType === 'project'
|
||||
? await projectsApi.searchFiles(searchId, searchQuery, 'settings', {
|
||||
signal: abortController.signal,
|
||||
})
|
||||
: await repoApi.searchFiles(searchId, searchQuery, 'settings', {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
|
||||
// Only process if this request wasn't aborted
|
||||
if (!abortController.signal.aborted) {
|
||||
@@ -118,7 +132,7 @@ export function MultiFileSearchTextarea({
|
||||
abortControllerRef.current.abort();
|
||||
}
|
||||
};
|
||||
}, [searchQuery, projectId]);
|
||||
}, [searchQuery, searchId, searchType]);
|
||||
|
||||
// Find current token boundaries based on cursor position
|
||||
const findCurrentToken = (text: string, cursorPosition: number) => {
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "The project you're looking for doesn't exist or has been deleted.",
|
||||
"viewProject": "View Project",
|
||||
"openInIDE": "Open in IDE",
|
||||
"createdDate": "Created {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "File paths or glob patterns (e.g., .env, config/*.json)"
|
||||
"createdDate": "Created {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "Copy Files",
|
||||
"helper": "Comma-separated list of files to copy from the original repository directory to the worktree. Useful for environment files like .env. Make sure these are gitignored!"
|
||||
"helper": "Comma-separated list of files to copy from the original repository directory to the worktree. Useful for environment files like .env. Make sure these are gitignored!",
|
||||
"placeholder": "File paths or glob patterns (e.g., .env, config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "Dev Server Script",
|
||||
|
||||
@@ -137,9 +137,10 @@
|
||||
},
|
||||
"browser": {
|
||||
"title": "Dev Server Preview",
|
||||
"startButton": "Start Dev Server",
|
||||
"stopButton": "Stop",
|
||||
"startingButton": "Start"
|
||||
"devServerFallback": "Dev Server"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "Enter URL..."
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "El proyecto que buscas no existe o ha sido eliminado.",
|
||||
"viewProject": "Ver Proyecto",
|
||||
"openInIDE": "Abrir en IDE",
|
||||
"createdDate": "Creado {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "Escribe una ruta o patrón glob (.env, config/*.json)"
|
||||
"createdDate": "Creado {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "Copiar Archivos",
|
||||
"helper": "Lista separada por comas de archivos para copiar del directorio del repositorio original al worktree. Útil para archivos de entorno como .env. ¡Asegúrate de que estén en gitignore!"
|
||||
"helper": "Lista separada por comas de archivos para copiar del directorio del repositorio original al worktree. Útil para archivos de entorno como .env. ¡Asegúrate de que estén en gitignore!",
|
||||
"placeholder": "Rutas de archivos o patrones glob (ej., .env, config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "Script del Servidor de Desarrollo",
|
||||
|
||||
@@ -373,9 +373,10 @@
|
||||
"noDevScriptHint": "Agrega un script de desarrollo en la configuración del proyecto para habilitar la vista previa.",
|
||||
"browser": {
|
||||
"title": "Vista previa del servidor de desarrollo",
|
||||
"startButton": "Iniciar servidor de desarrollo",
|
||||
"stopButton": "Detener",
|
||||
"startingButton": "Iniciar"
|
||||
"devServerFallback": "Servidor de desarrollo"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "Ingresar URL..."
|
||||
},
|
||||
"noServer": {
|
||||
"companionLink": "Ver guía de instalación",
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "お探しのプロジェクトは存在しないか、削除されました。",
|
||||
"viewProject": "プロジェクトを表示",
|
||||
"openInIDE": "IDEで開く",
|
||||
"createdDate": "作成日 {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "パスまたはglobパターンを入力 (.env, config/*.json)"
|
||||
"createdDate": "作成日 {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "ファイルをコピー",
|
||||
"helper": "元のリポジトリディレクトリからワークツリーにコピーするファイルのカンマ区切りリスト。.envなどの環境ファイルに役立ちます。gitignoreされていることを確認してください!"
|
||||
"helper": "元のリポジトリディレクトリからワークツリーにコピーするファイルのカンマ区切りリスト。.envなどの環境ファイルに役立ちます。gitignoreされていることを確認してください!",
|
||||
"placeholder": "ファイルパスまたはglobパターン(例:.env、config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "開発サーバースクリプト",
|
||||
|
||||
@@ -371,9 +371,10 @@
|
||||
},
|
||||
"browser": {
|
||||
"title": "開発サーバープレビュー",
|
||||
"startButton": "開発サーバーを開始",
|
||||
"stopButton": "停止",
|
||||
"startingButton": "開始"
|
||||
"devServerFallback": "開発サーバー"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "URLを入力..."
|
||||
},
|
||||
"noDevScript": "開発スクリプトが設定されていません",
|
||||
"noDevScriptHint": "プレビューを有効にするには、プロジェクト設定で開発スクリプトを追加してください。",
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "찾으시는 프로젝트가 존재하지 않거나 삭제되었습니다.",
|
||||
"viewProject": "프로젝트 보기",
|
||||
"openInIDE": "IDE에서 열기",
|
||||
"createdDate": "생성일 {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "경로 또는 glob 패턴 입력 (.env, config/*.json)"
|
||||
"createdDate": "생성일 {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "파일 복사",
|
||||
"helper": "원래 저장소 디렉토리에서 워크트리로 복사할 파일의 쉼표로 구분된 목록입니다. .env와 같은 환경 파일에 유용합니다. gitignore되었는지 확인하세요!"
|
||||
"helper": "원래 저장소 디렉토리에서 워크트리로 복사할 파일의 쉼표로 구분된 목록입니다. .env와 같은 환경 파일에 유용합니다. gitignore되었는지 확인하세요!",
|
||||
"placeholder": "파일 경로 또는 glob 패턴 (예: .env, config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "개발 서버 스크립트",
|
||||
|
||||
@@ -413,9 +413,10 @@
|
||||
},
|
||||
"browser": {
|
||||
"title": "개발 서버 미리보기",
|
||||
"startButton": "개발 서버 시작",
|
||||
"stopButton": "중지",
|
||||
"startingButton": "시작"
|
||||
"devServerFallback": "개발 서버"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "URL 입력..."
|
||||
},
|
||||
"noDevScript": "개발 스크립트가 설정되지 않았습니다",
|
||||
"noDevScriptHint": "미리보기를 활성화하려면 프로젝트 설정에서 개발 스크립트를 추가하세요."
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "您查找的项目不存在或已被删除。",
|
||||
"viewProject": "查看项目",
|
||||
"openInIDE": "在 IDE 中打开",
|
||||
"createdDate": "创建于 {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "文件路径或 glob 模式(例如:.env、config/*.json)"
|
||||
"createdDate": "创建于 {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "复制文件",
|
||||
"helper": "要从原始仓库目录复制到工作树的文件的逗号分隔列表。对 .env 等环境文件很有用。确保这些文件被 gitignore!"
|
||||
"helper": "要从原始仓库目录复制到工作树的文件的逗号分隔列表。对 .env 等环境文件很有用。确保这些文件被 gitignore!",
|
||||
"placeholder": "文件路径或 glob 模式(例如:.env、config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "开发服务器脚本",
|
||||
|
||||
@@ -130,9 +130,10 @@
|
||||
"noDevScriptHint": "在项目设置中添加开发脚本以启用预览。",
|
||||
"browser": {
|
||||
"title": "开发服务器预览",
|
||||
"startButton": "启动开发服务器",
|
||||
"stopButton": "停止",
|
||||
"startingButton": "启动"
|
||||
"devServerFallback": "开发服务器"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "输入 URL..."
|
||||
},
|
||||
"iframe": {
|
||||
"title": "开发服务器预览"
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
"projectNotFound": "您要查找的專案不存在或已被刪除。",
|
||||
"viewProject": "查看專案",
|
||||
"openInIDE": "在 IDE 中開啟",
|
||||
"createdDate": "建立於 {{date}}",
|
||||
"copyFilesPlaceholderWithSearch": "檔案路徑或 glob 模式(例如:.env、config/*.json)"
|
||||
"createdDate": "建立於 {{date}}"
|
||||
}
|
||||
|
||||
@@ -380,7 +380,8 @@
|
||||
},
|
||||
"copyFiles": {
|
||||
"label": "複製檔案",
|
||||
"helper": "要從原始儲存庫目錄複製到工作樹的檔案清單(以逗號分隔)。適合用於 .env 等環境檔案。請確保這些檔案已加入 gitignore!"
|
||||
"helper": "要從原始儲存庫目錄複製到工作樹的檔案清單(以逗號分隔)。適合用於 .env 等環境檔案。請確保這些檔案已加入 gitignore!",
|
||||
"placeholder": "檔案路徑或 glob 模式(例如:.env、config/*.json)"
|
||||
},
|
||||
"devServer": {
|
||||
"label": "開發伺服器腳本",
|
||||
|
||||
@@ -130,9 +130,10 @@
|
||||
"noDevScriptHint": "在專案設定中新增開發指令碼以啟用預覽。",
|
||||
"browser": {
|
||||
"title": "開發伺服器預覽",
|
||||
"startButton": "啟動開發伺服器",
|
||||
"stopButton": "停止",
|
||||
"startingButton": "啟動"
|
||||
"devServerFallback": "開發伺服器"
|
||||
},
|
||||
"urlInput": {
|
||||
"placeholder": "輸入 URL..."
|
||||
},
|
||||
"iframe": {
|
||||
"title": "開發伺服器預覽"
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
CreateProject,
|
||||
CreateProjectRepo,
|
||||
UpdateRepo,
|
||||
SearchMode,
|
||||
SearchResult,
|
||||
ShareTaskResponse,
|
||||
Task,
|
||||
@@ -283,7 +284,7 @@ export const projectsApi = {
|
||||
searchFiles: async (
|
||||
id: string,
|
||||
query: string,
|
||||
mode?: string,
|
||||
mode?: SearchMode,
|
||||
options?: RequestInit
|
||||
): Promise<SearchResult[]> => {
|
||||
const modeParam = mode ? `&mode=${encodeURIComponent(mode)}` : '';
|
||||
@@ -895,6 +896,20 @@ export const repoApi = {
|
||||
});
|
||||
return handleApiResponse<OpenEditorResponse>(response);
|
||||
},
|
||||
|
||||
searchFiles: async (
|
||||
repoId: string,
|
||||
query: string,
|
||||
mode?: SearchMode,
|
||||
options?: RequestInit
|
||||
): Promise<SearchResult[]> => {
|
||||
const modeParam = mode ? `&mode=${encodeURIComponent(mode)}` : '';
|
||||
const response = await makeRequest(
|
||||
`/api/repos/${repoId}/search?q=${encodeURIComponent(query)}${modeParam}`,
|
||||
options
|
||||
);
|
||||
return handleApiResponse<SearchResult[]>(response);
|
||||
},
|
||||
};
|
||||
|
||||
// Config APIs (backwards compatible)
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { useScriptPlaceholders } from '@/hooks/useScriptPlaceholders';
|
||||
import { AutoExpandingTextarea } from '@/components/ui/auto-expanding-textarea';
|
||||
import { MultiFileSearchTextarea } from '@/components/ui/multi-file-search-textarea';
|
||||
import { repoApi } from '@/lib/api';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { Repo, UpdateRepo } from 'shared/types';
|
||||
@@ -422,12 +423,14 @@ export function ReposSettings() {
|
||||
<Label htmlFor="copy-files">
|
||||
{t('settings.repos.scripts.copyFiles.label')}
|
||||
</Label>
|
||||
<AutoExpandingTextarea
|
||||
id="copy-files"
|
||||
<MultiFileSearchTextarea
|
||||
value={draft.copy_files}
|
||||
onChange={(e) => updateDraft({ copy_files: e.target.value })}
|
||||
placeholder=".env, .env.local"
|
||||
onChange={(value) => updateDraft({ copy_files: value })}
|
||||
placeholder={t(
|
||||
'settings.repos.scripts.copyFiles.placeholder'
|
||||
)}
|
||||
maxRows={6}
|
||||
repoId={selectedRepo.id}
|
||||
className="w-full px-3 py-2 border border-input bg-background text-foreground rounded-md focus:outline-none focus:ring-2 focus:ring-ring font-mono"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user