Implement ID-based routing and folder auto-generation from titles
Major features:
- Switch from slug-based to ID-based routing (/documents/123)
- Enable title editing with automatic slug/path regeneration
- Auto-generate folder structure from title slashes (e.g., Laravel/Livewire/Components)
- Persist sidebar folder open/close state using localStorage
- Remove slug unique constraint (ID routing makes it unnecessary)
- Implement recursive tree view with multi-level folder support
Architecture changes:
- DocumentService: Add generatePathAndSlug() for title-based path generation
- Routes: Change from {document:slug} to {document} for ID binding
- SidebarTree: Extract recursive rendering to partials/tree-item.blade.php
- Database: Remove unique constraint from documents.slug column
UI improvements:
- Display only last path component in sidebar (Components vs Laravel/Livewire/Components)
- Folder state persists across page navigation via localStorage
- Title field accepts slashes for folder organization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
107
src/app/Livewire/QuickSwitcher.php
Normal file
107
src/app/Livewire/QuickSwitcher.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Services\DocumentService;
|
||||
use Livewire\Component;
|
||||
use Livewire\Attributes\Computed;
|
||||
|
||||
class QuickSwitcher extends Component
|
||||
{
|
||||
public $search = '';
|
||||
public $selectedIndex = 0;
|
||||
|
||||
#[Computed]
|
||||
public function results()
|
||||
{
|
||||
if (empty($this->search)) {
|
||||
return Document::select('id', 'title', 'slug', 'path', 'updated_at')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(fn($doc) => [
|
||||
'id' => $doc->id,
|
||||
'title' => $doc->title,
|
||||
'slug' => $doc->slug,
|
||||
'directory' => dirname($doc->path),
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
// FULLTEXT検索を使用(日本語対応)
|
||||
$results = Document::select('id', 'title', 'slug', 'path', 'updated_at')
|
||||
->whereRaw('MATCH(title, content) AGAINST(? IN BOOLEAN MODE)', [$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();
|
||||
|
||||
// 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)
|
||||
{
|
||||
if ($propertyName === 'search') {
|
||||
$this->selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function selectNext()
|
||||
{
|
||||
$results = $this->results;
|
||||
if ($this->selectedIndex < count($results) - 1) {
|
||||
$this->selectedIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
public function selectPrevious()
|
||||
{
|
||||
if ($this->selectedIndex > 0) {
|
||||
$this->selectedIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
public function selectDocument()
|
||||
{
|
||||
$results = $this->results;
|
||||
if (isset($results[$this->selectedIndex])) {
|
||||
$document = $results[$this->selectedIndex];
|
||||
|
||||
// id が存在することを確認
|
||||
if (!empty($document['id'])) {
|
||||
return $this->redirect(route('documents.show', $document['id']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.quick-switcher');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user