feat(web): sélecteur de répertoire (parcourir au lieu de taper le chemin)
Ajoute un navigateur de dossiers réutilisable sur les deux champs qui exigeaient un chemin absolu tapé à la main : « Répertoire de travail » (nouvelle session) et « Chemin du dépôt » (ajout de repo). Un bouton « Parcourir… » déplie un panneau inline ; le champ texte reste pour taper/coller un chemin connu. Pour l'ajout de repo, les sous-dossiers qui sont des dépôts git sont signalés par un badge. Serveur : nouvel endpoint GET /api/v1/fs/list (authentifié par le hook preValidation global), ne renvoie que des noms de sous-dossiers (jamais de contenu de fichier). Valide le chemin via resolve() (clampe à la racine, neutralise « .. »), masque les dotfiles par défaut (showHidden), annote les dépôts git (markRepos), tolère les entrées illisibles. Tests : packages/server/test/fs-routes.test.ts (10 cas — listing, tri, hidden, markRepos, rejets 400/404, auth 401). Suite : 185/185 verts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,10 +28,18 @@
|
||||
{{ t('repos.pathLabel') }}
|
||||
<input v-model="newPath" type="text" class="input font-mono" :placeholder="t('repos.pathPlaceholder')" required />
|
||||
</label>
|
||||
<button type="button" class="btn whitespace-nowrap" @click="showPicker = !showPicker">{{ t('fs.browse') }}</button>
|
||||
<button type="submit" class="btn-primary whitespace-nowrap" :disabled="adding || newPath.trim() === ''">
|
||||
{{ adding ? t('repos.adding') : t('repos.add') }}
|
||||
</button>
|
||||
</form>
|
||||
<DirectoryPicker
|
||||
v-if="showPicker"
|
||||
mode="repo"
|
||||
:initial-path="newPath"
|
||||
@select="onPickPath"
|
||||
@close="showPicker = false"
|
||||
/>
|
||||
<p v-if="addError" class="text-sm text-red-400">{{ addError }}</p>
|
||||
|
||||
<p v-if="store.loadError" class="text-sm text-red-400">{{ store.loadError }}</p>
|
||||
@@ -53,6 +61,7 @@ import { useSessionsStore } from '../stores/sessions';
|
||||
import { usePushStore } from '../stores/push';
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher.vue';
|
||||
import RepoSection from '../components/RepoSection.vue';
|
||||
import DirectoryPicker from '../components/DirectoryPicker.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
@@ -62,9 +71,15 @@ const sessions = useSessionsStore();
|
||||
const push = usePushStore();
|
||||
|
||||
const newPath = ref('');
|
||||
const showPicker = ref(false);
|
||||
const adding = ref(false);
|
||||
const addError = ref<string | null>(null);
|
||||
|
||||
function onPickPath(path: string): void {
|
||||
newPath.value = path;
|
||||
showPicker.value = false;
|
||||
}
|
||||
|
||||
// 'denied'/'unsupported' sont des clés i18n ; tout autre message d'erreur est affiché tel quel.
|
||||
const pushErrorText = computed(() => {
|
||||
const e = push.error;
|
||||
|
||||
Reference in New Issue
Block a user