Paleta komend
View on GitHubDodaj źródło wyszukiwania
Przećwiczony przykład: jak osoby (@ann) dodano obok issues i projektów. Te same kroki dodają każdy nowy rodzaj wyniku. Wszystko idzie przez jeden wewnętrzny endpoint, GET /command-palette/search, opisany jako jedyny wyjątek od "brak JSON API" w ../architecture/05-scope-and-non-goals.mdArchitektura i technikalia projektuZakres i nie-celeRzeczy, które na pierwszy rzut oka wyglądają, jakby mogły brakować, były na wpół zbudowane albo warte "naprawienia", ale w rzeczywistości są celowe — przec….
Czego potrzebuje źródło
Prefiksu zapytania (opcjonalnie), zapytania repozytorium ograniczonego do członkostw użytkownika w projektach, payloadu w odpowiedzi i mapowania na wiersze palety. Trasa, walidacja (q ma 1–100 znaków), throttle i obsługa błędów jako JSON (command-palette/* w bootstrap/app.php) są wspólne; nie ruszasz ich.
Krok 1 — Repozytorium: ogranicz w zapytaniu
Plik: app/Repositories/UserRepository.php
public function searchMembersForUser(int $userId, string $q, int $limit = 8): SupportCollection
{
$escape = LikePattern::ESCAPE;
return DB::table('project_user as theirs')
->join('project_user as mine', 'mine.project_id', '=', 'theirs.project_id')
->join('users', 'users.id', '=', 'theirs.user_id')
->join('projects', 'projects.id', '=', 'theirs.project_id')
->where('mine.user_id', $userId)
->whereRaw("LOWER(users.name) LIKE ? ESCAPE '{$escape}'", [LikePattern::contains($q)])
// ...select, order prefix-first, limit
->get();
}
- Join po członkostwie to kontrola dostępu. Nigdy "znajdź, potem odfiltruj".
- Wzorce
LIKEzawsze buduj przezApp\Support\LikePatterniESCAPE '!', żeby wpisane%lub_pasowało do siebie. - Rankuj prefiks nad fragmentem, potem wg świeżości lub nazwy, i ogranicz liczbę wierszy.
Krok 2 — Serwis: skieruj zapytanie i ukształtuj payload
Plik: app/Services/CommandPaletteSearchService.php
if (str_starts_with($query, '@')) {
return $this->searchPeople($userId, trim(substr($query, 1)));
}
Każda odpowiedź ma wszystkie klucze (issues, projects, people), więc klient nigdy nie sprawdza ich obecności; źródło, które nie ma zastosowania, zwraca []. Daj każdemu wierszowi payloadu id, to co wiersz renderuje, i url zbudowany po stronie serwera przez route(..., absolute: false).
Krok 3 — Frontend: typ, prefiks, stan, mapowanie, renderowanie
-
Typ i grupa —
resources/js/types/Commands.ts: dodajPaletteSearchPerson, dodajpeopledoPaletteSearchResults, doCommandGroup,COMMAND_GROUP_ORDERiCOMMAND_GROUP_LABELS; dodaj grupę doGROUP_CATEGORYwcontext/CommandContext.tsx. -
Prefiks —
resources/js/utils/paletteQuery.ts:if (raw.startsWith('@')) { const rest = raw.slice(1).trim(); return { scope: 'people', text: rest, serverQuery: rest.length >= 1 ? `@${rest}`.slice(0, MAX_QUERY_LENGTH) : null, }; }serverQuery: nullnic nie wysyła; minimalną długość wybierasz osobno dla każdego źródła. -
Hook —
resources/js/hooks/usePaletteSearch.ts: dodaj pole do stanu idle i czytajdata.people ?? []. -
Wiersz —
resources/js/utils/paletteEntities.ts:export const personToCommand = (person: PaletteSearchPerson): Command => ({ id: `person.${person.id}.${person.project.id}`, title: `Show issues assigned to ${person.name}`, subtitle: person.project.name, group: 'people', icon: 'UserSearch', perform: () => router.visit(person.url), });Zadbaj, by
idbył unikalny dla wiersza (osoba może dzielić dwa projekty). -
Renderowanie —
resources/js/Components/Organisms/CommandPalette/CommandPalette.tsx: dodaj źródło do listyentities, potraktuj jego scope jakissueswentityOnlyi wemptyMessageoraz dodaj podpowiedź w stopce.
Lokalne wyniki fuzzy pokazują się pierwsze i natychmiast; grupy z serwera doczytują się pod spodem, ze skeletonem podczas ładowania i dyskretną linią (lokalne wyniki zostają), jeśli żądanie się nie powiedzie.
Testy
- Repozytorium/serwis (
tests/Feature/CommandPaletteSearchServiceTest.php): ranking, limit,%dopasowane dosłownie i użytkownik spoza projektu nigdy nie widzi wiersza — wymagane dla każdego źródła. - Kontroler (
tests/Feature/CommandPaletteControllerTest.php): payload przez HTTP i ta sama izolacja nie-członka. - Frontend:
paletteQuery.test.ts(prefiks i minimalna długość),paletteEntities.test.ts(wiersz),usePaletteSearch.test.ts(pole),CommandPalette.test.tsx(grupa, nawigacja, podpowiedź w stopce).
