feat: add search to the new session project picker (#3408)
* feat: add search to the new session project picker * fix: punctuate the project picker empty state
This commit is contained in:
@@ -189,7 +189,14 @@ and the send path reading the same grammar.
|
|||||||
also owns the advisory dirty state for the selected directory, clearing it as
|
also owns the advisory dirty state for the selected directory, clearing it as
|
||||||
soon as the target changes so a warning never names a previous branch.
|
soon as the target changes so a warning never names a previous branch.
|
||||||
- `ui/DraftTargetSelectors.tsx` owns the controlled project/worktree picker
|
- `ui/DraftTargetSelectors.tsx` owns the controlled project/worktree picker
|
||||||
state and registers its application shortcuts locally. The selectors only
|
state and registers its application shortcuts locally. The desktop project
|
||||||
|
picker is a searchable popup: it ranks the current projects with
|
||||||
|
`rankByQuery` over display label and path, keeps the query and the active
|
||||||
|
result as transient local state that resets on every close, and commits
|
||||||
|
through the existing project-change flow only on explicit activation.
|
||||||
|
Filtering changes the result area below the anchored input without moving
|
||||||
|
the search field. The
|
||||||
|
worktree Select and the mobile bottom sheets are unchanged. The selectors only
|
||||||
consume their shared prefix while the draft target UI is mounted.
|
consume their shared prefix while the draft target UI is mounted.
|
||||||
|
|
||||||
## Input recall ownership
|
## Input recall ownership
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Where a new session will run: the project and the directory within it.
|
* Where a new session will run: the project and the directory within it.
|
||||||
*
|
*
|
||||||
* Desktop uses inline selects; mobile uses bottom sheets, because a native
|
* Desktop uses a searchable project popup and an inline branch/worktree
|
||||||
* select over a keyboard-resized viewport is unusable. Both render the same
|
* select; mobile uses bottom sheets, because a native select over a
|
||||||
* options from the same hook, and both offer creating a worktree inline so the
|
* keyboard-resized viewport is unusable. Both render the same options from
|
||||||
* user does not have to leave the draft to make one.
|
* the same hook, and both offer creating a worktree inline so the user does
|
||||||
|
* not have to leave the draft to make one.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -12,8 +13,19 @@ import React from 'react';
|
|||||||
import { Icon } from '@/components/icon/Icon';
|
import { Icon } from '@/components/icon/Icon';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { shouldDismissDropdown } from '@/components/ui/dropdown-navigation';
|
import { Popover } from '@base-ui/react/popover';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { dropdownMenuPopupClass } from '@/components/ui/dropdown-menu.styles';
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from '@/components/ui/command';
|
||||||
|
import { handleDropdownNavigationKey, shouldDismissDropdown } from '@/components/ui/dropdown-navigation';
|
||||||
|
import { isIMECompositionEvent } from '@/lib/ime';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -28,6 +40,7 @@ import { useI18n } from '@/lib/i18n';
|
|||||||
import { matchesRankQuery, rankByQuery } from '@/lib/search/fuzzySearch';
|
import { matchesRankQuery, rankByQuery } from '@/lib/search/fuzzySearch';
|
||||||
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, ProjectIconImage } from '@/lib/projectMeta';
|
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, ProjectIconImage } from '@/lib/projectMeta';
|
||||||
import { createWorktreeDraft } from '@/lib/worktreeSessionCreator';
|
import { createWorktreeDraft } from '@/lib/worktreeSessionCreator';
|
||||||
|
import { shortcutRegistry } from '@/lib/shortcuts';
|
||||||
import { useKeybind } from '@/hooks/useKeybind';
|
import { useKeybind } from '@/hooks/useKeybind';
|
||||||
import type { Theme } from '@/types/theme';
|
import type { Theme } from '@/types/theme';
|
||||||
import { normalizePath } from '../attachments/filePaths';
|
import { normalizePath } from '../attachments/filePaths';
|
||||||
@@ -145,8 +158,34 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
|
|||||||
theme,
|
theme,
|
||||||
} = props;
|
} = props;
|
||||||
const [openPicker, setOpenPicker] = React.useState<'project' | 'worktree' | null>(null);
|
const [openPicker, setOpenPicker] = React.useState<'project' | 'worktree' | null>(null);
|
||||||
|
const [projectQuery, setProjectQuery] = React.useState('');
|
||||||
|
const [projectActiveId, setProjectActiveId] = React.useState<string | null>(null);
|
||||||
|
const [projectFocusReturn, setProjectFocusReturn] = React.useState(false);
|
||||||
const projectTriggerRef = React.useRef<HTMLButtonElement>(null);
|
const projectTriggerRef = React.useRef<HTMLButtonElement>(null);
|
||||||
const worktreeTriggerRef = React.useRef<HTMLButtonElement>(null);
|
const worktreeTriggerRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
const projectSearchRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
// Preserve Select's dialog portal and main-area containment.
|
||||||
|
const [projectPortalContainer, setProjectPortalContainer] = React.useState<HTMLElement | null>(null);
|
||||||
|
const [projectCollisionBoundary, setProjectCollisionBoundary] = React.useState<Element | null>(null);
|
||||||
|
const syncProjectPopupContainers = React.useCallback((target: EventTarget | null) => {
|
||||||
|
const element = target instanceof HTMLElement ? target : null;
|
||||||
|
const dialog = element?.closest('[data-slot="dialog-content"], [role="dialog"]');
|
||||||
|
setProjectPortalContainer(dialog instanceof HTMLElement ? dialog : null);
|
||||||
|
setProjectCollisionBoundary(element?.closest('main') ?? null);
|
||||||
|
}, []);
|
||||||
|
// The popover owns no shortcut suspension (unlike Select/DropdownMenu
|
||||||
|
// wrappers), so suspend global shortcuts while the project popup is
|
||||||
|
// open and restore them on close/unmount.
|
||||||
|
const projectSuspendRef = React.useRef<(() => void) | null>(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (openPicker !== 'project') return;
|
||||||
|
projectSuspendRef.current?.();
|
||||||
|
projectSuspendRef.current = shortcutRegistry.suspend();
|
||||||
|
return () => {
|
||||||
|
projectSuspendRef.current?.();
|
||||||
|
projectSuspendRef.current = null;
|
||||||
|
};
|
||||||
|
}, [openPicker]);
|
||||||
const handlePickerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
const handlePickerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
||||||
if (openPicker === null || !shouldDismissDropdown(event)) return;
|
if (openPicker === null || !shouldDismissDropdown(event)) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -156,6 +195,11 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
|
|||||||
|
|
||||||
useKeybind('open_draft_project_picker', () => {
|
useKeybind('open_draft_project_picker', () => {
|
||||||
projectTriggerRef.current?.focus();
|
projectTriggerRef.current?.focus();
|
||||||
|
setProjectFocusReturn(false);
|
||||||
|
setProjectQuery('');
|
||||||
|
setProjectActiveId(
|
||||||
|
projects.some((project) => project.id === selectedProject.id) ? selectedProject.id : null,
|
||||||
|
);
|
||||||
setOpenPicker('project');
|
setOpenPicker('project');
|
||||||
});
|
});
|
||||||
useKeybind('open_draft_worktree_picker', () => {
|
useKeybind('open_draft_worktree_picker', () => {
|
||||||
@@ -164,11 +208,43 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
|
|||||||
setOpenPicker('worktree');
|
setOpenPicker('worktree');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredProjects = React.useMemo(
|
||||||
|
() => openPicker === 'project'
|
||||||
|
? rankByQuery(projects, projectQuery, (project) => [getProjectDisplayLabel(project), project.path])
|
||||||
|
: projects,
|
||||||
|
[openPicker, projects, projectQuery],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transient search must never survive to the next opening, including
|
||||||
|
// picker switches and draft unmounts.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (openPicker !== 'project') {
|
||||||
|
setProjectQuery('');
|
||||||
|
setProjectActiveId(null);
|
||||||
|
}
|
||||||
|
}, [openPicker]);
|
||||||
|
// After a query or project-list change, retain the active ID only
|
||||||
|
// while it stays visible; otherwise fall to the first result (or none
|
||||||
|
// when the list is empty) so Enter cannot commit a hidden row.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (openPicker !== 'project') return;
|
||||||
|
setProjectActiveId((current) => {
|
||||||
|
if (current && filteredProjects.some((project) => project.id === current)) return current;
|
||||||
|
return filteredProjects[0]?.id ?? null;
|
||||||
|
});
|
||||||
|
}, [filteredProjects, openPicker]);
|
||||||
|
|
||||||
const handleProjectChange = (projectId: string) => {
|
const handleProjectChange = (projectId: string) => {
|
||||||
onProjectChange(projectId);
|
onProjectChange(projectId);
|
||||||
|
setProjectFocusReturn(true);
|
||||||
setOpenPicker(null);
|
setOpenPicker(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProjectSelect = (projectId: string) => {
|
||||||
|
if (!filteredProjects.some((project) => project.id === projectId)) return;
|
||||||
|
handleProjectChange(projectId);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDirectoryChange = (directory: string) => {
|
const handleDirectoryChange = (directory: string) => {
|
||||||
onDirectoryChange(directory);
|
onDirectoryChange(directory);
|
||||||
setOpenPicker(null);
|
setOpenPicker(null);
|
||||||
@@ -176,33 +252,152 @@ export function DraftTargetSelectors(props: DraftTargetProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-1.5 flex min-w-0 items-center gap-1.5 px-0.5">
|
<div className="mb-1.5 flex min-w-0 items-center gap-1.5 px-0.5">
|
||||||
<Select
|
{/* Plain popover (not a menu): the popup owns a combobox +
|
||||||
value={selectedProject.id}
|
listbox, so menu/menuitem semantics would be wrong here. */}
|
||||||
|
<Popover.Root
|
||||||
open={openPicker === 'project'}
|
open={openPicker === 'project'}
|
||||||
onOpenChange={(open) => setOpenPicker(open ? 'project' : null)}
|
onOpenChange={(open, eventDetails) => {
|
||||||
onValueChange={handleProjectChange}
|
if (open) {
|
||||||
disableGlobalShortcuts
|
setProjectFocusReturn(false);
|
||||||
|
// Seed the active row from the committed project so
|
||||||
|
// Enter without typing repeats the current choice.
|
||||||
|
// The clamp below keeps it when still visible and
|
||||||
|
// falls to the first result otherwise.
|
||||||
|
setProjectQuery('');
|
||||||
|
setProjectActiveId(
|
||||||
|
projects.some((project) => project.id === selectedProject.id)
|
||||||
|
? selectedProject.id
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
setOpenPicker('project');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setProjectQuery('');
|
||||||
|
setProjectActiveId(null);
|
||||||
|
const reason = eventDetails?.reason;
|
||||||
|
setProjectFocusReturn(reason === 'escape-key');
|
||||||
|
setOpenPicker(null);
|
||||||
|
}}
|
||||||
|
onOpenChangeComplete={(open) => {
|
||||||
|
// Return focus after Base UI finishes closing so the
|
||||||
|
// trigger itself, not document body, keeps keyboard flow.
|
||||||
|
if (!open && projectFocusReturn) {
|
||||||
|
projectTriggerRef.current?.focus();
|
||||||
|
setProjectFocusReturn(false);
|
||||||
|
}
|
||||||
|
// Focus the search once the popup mounts; the opening
|
||||||
|
// shortcut focuses the trigger first.
|
||||||
|
if (open && openPicker === 'project') projectSearchRef.current?.focus();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger
|
<Popover.Trigger
|
||||||
ref={projectTriggerRef}
|
render={
|
||||||
onKeyDown={handlePickerKeyDown}
|
<Button
|
||||||
size="sm"
|
ref={projectTriggerRef}
|
||||||
className="h-7 min-w-0 w-fit max-w-[42vw] sm:max-w-[18rem] border-transparent bg-transparent px-1.5 hover:bg-transparent data-[popup-open]:bg-transparent"
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
aria-haspopup="dialog"
|
||||||
|
className="h-7 min-w-0 w-fit max-w-[42vw] justify-start gap-1 px-1.5 normal-case sm:max-w-[18rem]"
|
||||||
|
onPointerDownCapture={(event) => syncProjectPopupContainers(event.currentTarget)}
|
||||||
|
onFocusCapture={(event) => syncProjectPopupContainers(event.currentTarget)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<SelectValue>
|
<span className="flex min-w-0 items-center gap-1.5">
|
||||||
{selectedProject.kind === 'chat'
|
{selectedProject.kind === 'chat'
|
||||||
? <span className="truncate">{t('chat.chatInput.chooseProject')}</span>
|
? <span className="truncate">{t('chat.chatInput.chooseProject')}</span>
|
||||||
: <ProjectLabel project={selectedProject} theme={theme} />}
|
: <ProjectLabel project={selectedProject} theme={theme} />}
|
||||||
</SelectValue>
|
<Icon name="arrow-down-s" className="size-4 shrink-0 opacity-50" />
|
||||||
</SelectTrigger>
|
</span>
|
||||||
<SelectContent side="top" collisionAvoidance={{ side: 'none' }} constrainToMain fitContent onKeyDown={handlePickerKeyDown}>
|
</Popover.Trigger>
|
||||||
{projects.map((project) => (
|
<Popover.Portal container={projectPortalContainer ?? undefined}>
|
||||||
<SelectItem key={project.id} value={project.id} showSelectedBackground={false} className="max-w-[24rem] truncate">
|
{/* side="bottom" anchors the popup's top edge at the
|
||||||
<ProjectLabel project={project} theme={theme} />
|
trigger: the search field stays fixed at the
|
||||||
</SelectItem>
|
selector's level while filtering, and only the
|
||||||
))}
|
results area below changes height. Disabling side
|
||||||
</SelectContent>
|
flips keeps the input stationary during filtering.
|
||||||
</Select>
|
Horizontal shifting keeps the popup inside main. The
|
||||||
|
--available-height cap comes free from the shared
|
||||||
|
popup class, so the list scrolls within the space
|
||||||
|
below the trigger. */}
|
||||||
|
<Popover.Positioner
|
||||||
|
side="bottom"
|
||||||
|
align="start"
|
||||||
|
sideOffset={4}
|
||||||
|
collisionAvoidance={{ side: 'none' }}
|
||||||
|
collisionBoundary={projectCollisionBoundary ?? undefined}
|
||||||
|
className="app-region-no-drag z-50"
|
||||||
|
>
|
||||||
|
<Popover.Popup
|
||||||
|
role="dialog"
|
||||||
|
aria-label={t('chat.chatInput.draftPicker.projectTitle')}
|
||||||
|
className={cn(dropdownMenuPopupClass, 'flex w-72 max-w-[calc(100vw-2rem)] flex-col p-0')}
|
||||||
|
initialFocus={false}
|
||||||
|
finalFocus={false}
|
||||||
|
>
|
||||||
|
{/* Filtering and ordering are owned by rankByQuery above;
|
||||||
|
cmdk's own filter would re-filter and reorder the
|
||||||
|
already-ranked rows. */}
|
||||||
|
<Command
|
||||||
|
className="min-h-0 flex-1"
|
||||||
|
shouldFilter={false}
|
||||||
|
value={projectActiveId ?? undefined}
|
||||||
|
onValueChange={setProjectActiveId}
|
||||||
|
>
|
||||||
|
<CommandInput
|
||||||
|
ref={projectSearchRef}
|
||||||
|
aria-label={t('chat.chatInput.draftPicker.searchProjects')}
|
||||||
|
placeholder={t('chat.chatInput.draftPicker.searchProjects')}
|
||||||
|
value={projectQuery}
|
||||||
|
onValueChange={setProjectQuery}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
// Command owns active-item navigation, so only
|
||||||
|
// translate the repository's Ctrl+N/P
|
||||||
|
// convention into arrows at the input.
|
||||||
|
// IME-composing keys must never move the
|
||||||
|
// active row or dismiss the popup.
|
||||||
|
if (isIMECompositionEvent(event)) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleDropdownNavigationKey(event, (navigationKey) => {
|
||||||
|
event.currentTarget.dispatchEvent(new KeyboardEvent('keydown', {
|
||||||
|
key: navigationKey,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CommandList label={t('chat.chatInput.draftPicker.projectTitle')}>
|
||||||
|
{filteredProjects.length === 0 ? (
|
||||||
|
<div role="status" className="px-3 py-6 text-center typography-ui-label text-muted-foreground">
|
||||||
|
{t('chat.chatInput.draftPicker.noProjectsFound')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredProjects.map((project) => (
|
||||||
|
<CommandItem
|
||||||
|
key={project.id}
|
||||||
|
value={project.id}
|
||||||
|
onSelect={handleProjectSelect}
|
||||||
|
aria-current={project.id === selectedProject.id ? true : undefined}
|
||||||
|
className="max-w-full"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate">
|
||||||
|
<ProjectLabel project={project} theme={theme} />
|
||||||
|
</span>
|
||||||
|
{project.id === selectedProject.id ? (
|
||||||
|
<Icon name="check" className="size-4 shrink-0 text-muted-foreground" />
|
||||||
|
) : null}
|
||||||
|
</CommandItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</Popover.Popup>
|
||||||
|
</Popover.Positioner>
|
||||||
|
</Popover.Portal>
|
||||||
|
</Popover.Root>
|
||||||
|
|
||||||
{showBranchSelector ? (
|
{showBranchSelector ? (
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -2185,6 +2185,7 @@ export const dict = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'Projekt',
|
'chat.chatInput.draftPicker.projectTitle': 'Projekt',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'Projekte durchsuchen...',
|
'chat.chatInput.draftPicker.searchProjects': 'Projekte durchsuchen...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'Branches durchsuchen...',
|
'chat.chatInput.draftPicker.searchBranches': 'Branches durchsuchen...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'Keine Projekte gefunden.',
|
||||||
'chat.chatInput.worktrees': 'Worktrees',
|
'chat.chatInput.worktrees': 'Worktrees',
|
||||||
'chat.chatInput.worktreeNew': '+ Neu',
|
'chat.chatInput.worktreeNew': '+ Neu',
|
||||||
'chat.chatInput.drop.insertMention': 'Hier ablegen, um als Erwähnung einzufügen',
|
'chat.chatInput.drop.insertMention': 'Hier ablegen, um als Erwähnung einzufügen',
|
||||||
|
|||||||
@@ -2406,6 +2406,7 @@ export const dict = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'Project',
|
'chat.chatInput.draftPicker.projectTitle': 'Project',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'Search projects...',
|
'chat.chatInput.draftPicker.searchProjects': 'Search projects...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'Search branches...',
|
'chat.chatInput.draftPicker.searchBranches': 'Search branches...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'No projects found.',
|
||||||
'chat.chatInput.worktrees': 'Worktrees',
|
'chat.chatInput.worktrees': 'Worktrees',
|
||||||
'chat.chatInput.worktreeNew': '+ New',
|
'chat.chatInput.worktreeNew': '+ New',
|
||||||
'chat.chatInput.drop.insertMention': 'Drop to insert as mention',
|
'chat.chatInput.drop.insertMention': 'Drop to insert as mention',
|
||||||
|
|||||||
@@ -2372,6 +2372,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.chatInput.draftPicker.projectTitle": "Proyecto",
|
"chat.chatInput.draftPicker.projectTitle": "Proyecto",
|
||||||
"chat.chatInput.draftPicker.searchProjects": "Buscar proyectos...",
|
"chat.chatInput.draftPicker.searchProjects": "Buscar proyectos...",
|
||||||
"chat.chatInput.draftPicker.searchBranches": "Buscar ramas...",
|
"chat.chatInput.draftPicker.searchBranches": "Buscar ramas...",
|
||||||
|
"chat.chatInput.draftPicker.noProjectsFound": "No se encontraron proyectos.",
|
||||||
"chat.chatInput.worktrees": "Worktrees",
|
"chat.chatInput.worktrees": "Worktrees",
|
||||||
"chat.chatInput.worktreeNew": "+ Nuevo",
|
"chat.chatInput.worktreeNew": "+ Nuevo",
|
||||||
"chat.chatInput.drop.insertMention": "Suelta para insertar como mención",
|
"chat.chatInput.drop.insertMention": "Suelta para insertar como mención",
|
||||||
|
|||||||
@@ -2114,6 +2114,7 @@ export const dict = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'Projet',
|
'chat.chatInput.draftPicker.projectTitle': 'Projet',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'Rechercher des projets...',
|
'chat.chatInput.draftPicker.searchProjects': 'Rechercher des projets...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'Rechercher des branches...',
|
'chat.chatInput.draftPicker.searchBranches': 'Rechercher des branches...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'Aucun projet trouvé.',
|
||||||
'chat.chatInput.worktrees': 'Worktrees',
|
'chat.chatInput.worktrees': 'Worktrees',
|
||||||
'chat.chatInput.worktreeNew': '+ Nouveau',
|
'chat.chatInput.worktreeNew': '+ Nouveau',
|
||||||
'chat.chatInput.drop.insertMention': 'Déposer pour insérer comme mention',
|
'chat.chatInput.drop.insertMention': 'Déposer pour insérer comme mention',
|
||||||
|
|||||||
@@ -2405,6 +2405,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'プロジェクト',
|
'chat.chatInput.draftPicker.projectTitle': 'プロジェクト',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'プロジェクトを検索...',
|
'chat.chatInput.draftPicker.searchProjects': 'プロジェクトを検索...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'ブランチを検索...',
|
'chat.chatInput.draftPicker.searchBranches': 'ブランチを検索...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'プロジェクトが見つかりません。',
|
||||||
'chat.chatInput.worktrees': 'ワークツリー',
|
'chat.chatInput.worktrees': 'ワークツリー',
|
||||||
'chat.chatInput.worktreeNew': '+ 新規',
|
'chat.chatInput.worktreeNew': '+ 新規',
|
||||||
'chat.chatInput.drop.insertMention': 'ドロップしてメンションとして挿入',
|
'chat.chatInput.drop.insertMention': 'ドロップしてメンションとして挿入',
|
||||||
|
|||||||
@@ -2406,6 +2406,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': '프로젝트',
|
'chat.chatInput.draftPicker.projectTitle': '프로젝트',
|
||||||
'chat.chatInput.draftPicker.searchProjects': '프로젝트 검색...',
|
'chat.chatInput.draftPicker.searchProjects': '프로젝트 검색...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': '브랜치 검색...',
|
'chat.chatInput.draftPicker.searchBranches': '브랜치 검색...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': '프로젝트를 찾을 수 없습니다.',
|
||||||
'chat.chatInput.worktrees': '워크트리',
|
'chat.chatInput.worktrees': '워크트리',
|
||||||
'chat.chatInput.worktreeNew': '+ 새로 만들기',
|
'chat.chatInput.worktreeNew': '+ 새로 만들기',
|
||||||
'chat.chatInput.drop.insertMention': '여기에 놓아 멘션으로 추가',
|
'chat.chatInput.drop.insertMention': '여기에 놓아 멘션으로 추가',
|
||||||
|
|||||||
@@ -1267,6 +1267,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'Projekt',
|
'chat.chatInput.draftPicker.projectTitle': 'Projekt',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'Szukaj projektów...',
|
'chat.chatInput.draftPicker.searchProjects': 'Szukaj projektów...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'Szukaj gałęzi...',
|
'chat.chatInput.draftPicker.searchBranches': 'Szukaj gałęzi...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'Nie znaleziono projektów.',
|
||||||
'chat.chatInput.drop.attachFiles': 'Drop files here to attach',
|
'chat.chatInput.drop.attachFiles': 'Drop files here to attach',
|
||||||
'chat.chatInput.drop.insertMention': 'Drop to insert as mention',
|
'chat.chatInput.drop.insertMention': 'Drop to insert as mention',
|
||||||
'chat.chatInput.fileFallback': 'file',
|
'chat.chatInput.fileFallback': 'file',
|
||||||
|
|||||||
@@ -2372,6 +2372,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.chatInput.draftPicker.projectTitle": "Projeto",
|
"chat.chatInput.draftPicker.projectTitle": "Projeto",
|
||||||
"chat.chatInput.draftPicker.searchProjects": "Buscar projetos...",
|
"chat.chatInput.draftPicker.searchProjects": "Buscar projetos...",
|
||||||
"chat.chatInput.draftPicker.searchBranches": "Buscar branches...",
|
"chat.chatInput.draftPicker.searchBranches": "Buscar branches...",
|
||||||
|
"chat.chatInput.draftPicker.noProjectsFound": "Nenhum projeto encontrado.",
|
||||||
"chat.chatInput.worktrees": "Worktrees",
|
"chat.chatInput.worktrees": "Worktrees",
|
||||||
"chat.chatInput.worktreeNew": "+ Novo",
|
"chat.chatInput.worktreeNew": "+ Novo",
|
||||||
"chat.chatInput.drop.insertMention": "Solte para inserir como menção",
|
"chat.chatInput.drop.insertMention": "Solte para inserir como menção",
|
||||||
|
|||||||
@@ -2346,6 +2346,7 @@ export const dict = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': 'Proje',
|
'chat.chatInput.draftPicker.projectTitle': 'Proje',
|
||||||
'chat.chatInput.draftPicker.searchProjects': 'Projelerde ara...',
|
'chat.chatInput.draftPicker.searchProjects': 'Projelerde ara...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': 'Branch\'lerde ara...',
|
'chat.chatInput.draftPicker.searchBranches': 'Branch\'lerde ara...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': 'Proje bulunamadı.',
|
||||||
'chat.chatInput.worktrees': 'Worktree\'ler',
|
'chat.chatInput.worktrees': 'Worktree\'ler',
|
||||||
'chat.chatInput.worktreeNew': '+ Yeni',
|
'chat.chatInput.worktreeNew': '+ Yeni',
|
||||||
'chat.chatInput.drop.insertMention': 'Mention olarak eklemek için bırak',
|
'chat.chatInput.drop.insertMention': 'Mention olarak eklemek için bırak',
|
||||||
|
|||||||
@@ -2372,6 +2372,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
"chat.chatInput.draftPicker.projectTitle": "Проєкт",
|
"chat.chatInput.draftPicker.projectTitle": "Проєкт",
|
||||||
"chat.chatInput.draftPicker.searchProjects": "Пошук проєктів...",
|
"chat.chatInput.draftPicker.searchProjects": "Пошук проєктів...",
|
||||||
"chat.chatInput.draftPicker.searchBranches": "Пошук гілок...",
|
"chat.chatInput.draftPicker.searchBranches": "Пошук гілок...",
|
||||||
|
"chat.chatInput.draftPicker.noProjectsFound": "Проєктів не знайдено.",
|
||||||
"chat.chatInput.worktrees": "Worktree",
|
"chat.chatInput.worktrees": "Worktree",
|
||||||
"chat.chatInput.worktreeNew": "+ Новий",
|
"chat.chatInput.worktreeNew": "+ Новий",
|
||||||
"chat.chatInput.drop.insertMention": "Відпустіть, щоб вставити як згадку",
|
"chat.chatInput.drop.insertMention": "Відпустіть, щоб вставити як згадку",
|
||||||
|
|||||||
@@ -2372,6 +2372,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': '项目',
|
'chat.chatInput.draftPicker.projectTitle': '项目',
|
||||||
'chat.chatInput.draftPicker.searchProjects': '搜索项目...',
|
'chat.chatInput.draftPicker.searchProjects': '搜索项目...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': '搜索分支...',
|
'chat.chatInput.draftPicker.searchBranches': '搜索分支...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': '未找到项目。',
|
||||||
'chat.chatInput.worktrees': '工作树',
|
'chat.chatInput.worktrees': '工作树',
|
||||||
'chat.chatInput.worktreeNew': '+ 新建',
|
'chat.chatInput.worktreeNew': '+ 新建',
|
||||||
'chat.chatInput.drop.insertMention': '释放以插入为提及',
|
'chat.chatInput.drop.insertMention': '释放以插入为提及',
|
||||||
|
|||||||
@@ -2376,6 +2376,7 @@ export const dict: Record<I18nKey, string> = {
|
|||||||
'chat.chatInput.draftPicker.projectTitle': '專案',
|
'chat.chatInput.draftPicker.projectTitle': '專案',
|
||||||
'chat.chatInput.draftPicker.searchProjects': '搜尋專案...',
|
'chat.chatInput.draftPicker.searchProjects': '搜尋專案...',
|
||||||
'chat.chatInput.draftPicker.searchBranches': '搜尋分支...',
|
'chat.chatInput.draftPicker.searchBranches': '搜尋分支...',
|
||||||
|
'chat.chatInput.draftPicker.noProjectsFound': '找不到專案。',
|
||||||
'chat.chatInput.worktrees': 'Worktree',
|
'chat.chatInput.worktrees': 'Worktree',
|
||||||
'chat.chatInput.worktreeNew': '+ 新增',
|
'chat.chatInput.worktreeNew': '+ 新增',
|
||||||
'chat.chatInput.drop.insertMention': '放開以插入為提及',
|
'chat.chatInput.drop.insertMention': '放開以插入為提及',
|
||||||
|
|||||||
Reference in New Issue
Block a user