Make QuickSwitcher search across all locales

Delegates to DocumentService::search which queries DocumentTranslation
and collapses to distinct documents. Display titles use the Document
title accessor (current locale + fallback).
This commit is contained in:
Yutaka Kurosaki
2026-05-10 12:43:01 +09:00
parent 0100a0afb4
commit c9586612f5
2 changed files with 38 additions and 48 deletions
+13 -48
View File
@@ -4,65 +4,32 @@
use App\Models\Document; use App\Models\Document;
use App\Services\DocumentService; use App\Services\DocumentService;
use Livewire\Component;
use Livewire\Attributes\Computed; use Livewire\Attributes\Computed;
use Livewire\Component;
class QuickSwitcher extends Component class QuickSwitcher extends Component
{ {
public $search = ''; public string $search = '';
public $selectedIndex = 0; public int $selectedIndex = 0;
#[Computed] #[Computed]
public function results() public function results()
{ {
if (empty($this->search)) { if (empty($this->search)) {
return Document::select('id', 'title', 'slug', 'path', 'updated_at') $documents = Document::with('translations')
->orderBy('updated_at', 'desc') ->orderBy('updated_at', 'desc')
->limit(10) ->limit(10)
->get() ->get();
->map(fn($doc) => [ } else {
'id' => $doc->id, $documents = app(DocumentService::class)->search($this->search, 10);
'title' => $doc->title,
'slug' => $doc->slug,
'directory' => dirname($doc->path),
])
->toArray();
} }
// FULLTEXT検索を使用(日本語対応) return $documents->map(fn ($doc) => [
$results = Document::select('id', 'title', 'slug', 'path', 'updated_at') 'id' => $doc->id,
->whereRaw('MATCH(title, content) AGAINST(? IN BOOLEAN MODE)', [$this->search]) 'title' => $doc->title,
->orderBy('updated_at', 'desc') 'slug' => $doc->slug,
->limit(10) 'directory' => dirname($doc->path),
->get() ])->values()->toArray();
->map(fn($doc) => [
'id' => $doc->id,
'title' => $doc->title,
'slug' => $doc->slug,
'directory' => dirname($doc->path),
])
->toArray();
// FULLTEXT検索で結果がない場合は LIKE 検索にフォールバック
if (empty($results)) {
$results = Document::select('id', 'title', 'slug', 'path', 'updated_at')
->where(function($query) {
$query->where('title', 'like', '%' . $this->search . '%')
->orWhere('content', 'like', '%' . $this->search . '%');
})
->orderBy('updated_at', 'desc')
->limit(10)
->get()
->map(fn($doc) => [
'id' => $doc->id,
'title' => $doc->title,
'slug' => $doc->slug,
'directory' => dirname($doc->path),
])
->toArray();
}
return $results;
} }
public function updated($propertyName) public function updated($propertyName)
@@ -92,8 +59,6 @@ public function selectDocument()
$results = $this->results; $results = $this->results;
if (isset($results[$this->selectedIndex])) { if (isset($results[$this->selectedIndex])) {
$document = $results[$this->selectedIndex]; $document = $results[$this->selectedIndex];
// slug が存在することを確認
if (!empty($document['slug'])) { if (!empty($document['slug'])) {
return $this->redirect(route('documents.show', $document['slug'])); return $this->redirect(route('documents.show', $document['slug']));
} }
+25
View File
@@ -102,4 +102,29 @@ public function test_editor_for_missing_locale_shows_empty_form_with_new_locale_
// The blade should render a tab marked active for ja with empty inputs // The blade should render a tab marked active for ja with empty inputs
$response->assertSeeText(__('messages.locale_names.ja', [], 'en')); $response->assertSeeText(__('messages.locale_names.ja', [], 'en'));
} }
public function test_quick_switcher_finds_documents_by_any_locale_title(): void
{
$doc = Document::factory()->create(['default_locale' => 'en', 'slug' => 'qs']);
$doc->translations()->where('locale', 'en')->update(['title' => 'Getting Started', 'content' => 'EN body']);
DocumentTranslation::factory()->create([
'document_id' => $doc->id,
'locale' => 'ja',
'title' => 'はじめに',
'content' => '本文',
]);
$component = \Livewire\Livewire::test(\App\Livewire\QuickSwitcher::class)
->set('search', 'はじめに');
$results = $component->get('results');
$this->assertCount(1, $results);
$this->assertSame($doc->id, $results[0]['id']);
$component2 = \Livewire\Livewire::test(\App\Livewire\QuickSwitcher::class)
->set('search', 'Getting');
$results2 = $component2->get('results');
$this->assertCount(1, $results2);
$this->assertSame($doc->id, $results2[0]['id']);
}
} }