Compare commits

...

2 Commits

Author SHA1 Message Date
33fef93ce0 Update README with comprehensive project documentation
- Add detailed feature overview (wiki-links, folders, multi-language, responsive)
- Include complete installation and setup instructions
- Document project structure and key concepts
- Add development workflow and common commands
- Include troubleshooting section
- Update technology stack and credits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:19:43 +09:00
e14cc5dd43 Implement responsive design for mobile and tablet devices
- Add hamburger menu for mobile sidebar with slide-out animation
- Make header navigation responsive with icon-only buttons on mobile
- Adjust document viewer, editor, and quick switcher layouts for smaller screens
- Preserve all existing functionality including localStorage folder state persistence
- Use Tailwind responsive utilities (sm:, md:, lg:) for progressive enhancement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:17:50 +09:00
5 changed files with 418 additions and 95 deletions

View File

@@ -1,59 +1,332 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> # Knowledge Base System
<p align="center"> An Obsidian-like knowledge base system built with Laravel 12, Livewire v3, and Alpine.js. Create, organize, and link your documents with wiki-style references and a powerful search interface.
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel ## Features
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: ### Core Functionality
- **Markdown-based documents** with live preview using EasyMDE editor
- **Wiki-style linking** with `[[Document Title]]` syntax
- **Automatic backlinks** - see which documents reference the current page
- **Folder organization** - use `/` in titles to auto-organize into folders (e.g., `Laravel/Livewire/Components`)
- **Quick switcher** - Press `Ctrl+K` to instantly search and navigate
- **Full-text search** - MySQL FULLTEXT index with ngram tokenizer for multilingual support
- **ID-based routing** - Clean URLs with guaranteed uniqueness
- [Simple, fast routing engine](https://laravel.com/docs/routing). ### Multi-Language Support
- [Powerful dependency injection container](https://laravel.com/docs/container). Interface available in **16 languages**:
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. - English, 日本語 (Japanese)
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). - 简体中文, 繁體中文 (Simplified/Traditional Chinese)
- Database agnostic [schema migrations](https://laravel.com/docs/migrations). - 한국어 (Korean)
- [Robust background job processing](https://laravel.com/docs/queues). - हिन्दी (Hindi), Tiếng Việt (Vietnamese), Türkçe (Turkish)
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). - Deutsch, Français, Español, Português (Brasil)
- Русский, Українська, Italiano, Polski
Laravel is accessible, powerful, and provides tools required for large, robust applications. Language preferences persist for both authenticated and guest users via cookies.
## Learning Laravel ### Responsive Design
- **Mobile-first** interface with hamburger menu
- **Tablet and desktop** optimized layouts
- **Touch-friendly** navigation
- All features work seamlessly across devices
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application. ### User Management
- **Role-based access** - Admin and regular user roles
- **User authentication** - Laravel Breeze integration
- **Profile management** - Update name, email, password, and language preference
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. ## Technology Stack
## Laravel Sponsors - **Backend**: Laravel 12.0 (PHP 8.2+)
- **Frontend**: Livewire v3.7.0, Alpine.js, Tailwind CSS
- **Database**: MySQL 8.0 with FULLTEXT indexing
- **Markdown**: league/commonmark for rendering
- **Editor**: EasyMDE (markdown editor)
- **Docker**: Custom containerized environment
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). ## Prerequisites
### Premium Partners - Docker and Docker Compose
- Node.js 18+ (for asset compilation)
- Git
- **[Vehikl](https://vehikl.com)** ## Installation
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** ### 1. Clone the repository
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)** ```bash
- **[DevSquad](https://devsquad.com/hire-laravel-developers)** git clone <repository-url>
- **[Redberry](https://redberry.international/laravel-development)** cd knowledge-base
- **[Active Logic](https://activelogic.com)** ```
### 2. Start Docker services
```bash
docker compose up -d
```
This starts:
- Nginx: `http://localhost:9700`
- phpMyAdmin: `http://localhost:9701`
- MySQL: `localhost:9702`
- MailHog: `http://localhost:9725`
### 3. Install dependencies
```bash
# Inside the src/ directory
cd src
# Install PHP dependencies
docker compose exec php composer install
# Install Node dependencies
npm install
```
### 4. Configure environment
```bash
# Copy environment file
cp .env.example .env
# Generate application key
docker compose exec php php artisan key:generate
```
### 5. Set up database
```bash
# Run migrations
docker compose exec php php artisan migrate
# Seed initial user (admin@example.com / password)
docker compose exec php php artisan db:seed --class=UserSeeder
# Initialize sample documents (optional)
docker compose exec php php artisan docs:init
```
### 6. Build frontend assets
```bash
# Development mode with hot reload
npm run dev
# Or production build
npm run build
```
### 7. Access the application
Open `http://localhost:9700` in your browser.
**Default credentials**:
- Email: `admin@example.com`
- Password: `password`
## Development
### Running the dev environment
```bash
# Start all services (server, queue, logs, Vite)
docker compose exec php composer dev
```
### Running tests
```bash
docker compose exec php php artisan test
```
### Common commands
```bash
# Access PHP container shell
docker compose exec php bash
# Clear caches
docker compose exec php php artisan config:clear
docker compose exec php php artisan cache:clear
docker compose exec php php artisan view:clear
# Publish Livewire assets (after updates)
docker compose exec php php artisan livewire:publish --assets
```
## Project Structure
```
src/
├── app/
│ ├── Http/
│ │ ├── Controllers/
│ │ │ └── LocaleController.php # Language switching
│ │ └── Middleware/
│ │ └── SetLocale.php # Multi-language support
│ ├── Livewire/
│ │ ├── DocumentEditor.php # Create/edit documents
│ │ ├── DocumentViewer.php # Display documents
│ │ ├── QuickSwitcher.php # Ctrl+K search modal
│ │ └── SidebarTree.php # Folder tree navigation
│ ├── Models/
│ │ ├── Document.php # Document model
│ │ ├── DocumentLink.php # Wiki-style links
│ │ └── RecentDocument.php # Access history
│ └── Services/
│ └── DocumentService.php # Document business logic
├── database/
│ └── migrations/ # Database schema
├── lang/ # Translation files (16 languages)
├── resources/
│ ├── css/
│ │ └── app.css # Tailwind + custom styles
│ ├── js/
│ │ └── app.js # Alpine.js initialization
│ └── views/
│ ├── layouts/
│ │ └── knowledge-base.blade.php # Main layout
│ └── livewire/ # Livewire component views
└── routes/
└── web.php # Application routes
```
## Key Concepts
### Document Organization
Documents are organized using **virtual paths** derived from titles:
```php
Title: "Laravel/Livewire/Components"
→ Path: "Laravel/Livewire/Components.md"
→ Slug: "components"
→ Sidebar: Nested under Laravel → Livewire → Components
```
No manual directory field needed - just use `/` in the title!
### Wiki-Style Links
Create links between documents using double brackets:
```markdown
See [[Getting Started]] for more information.
Links to [[Uncreated Pages]] appear in red.
```
Links are automatically:
- Extracted and stored in the `document_links` table
- Rendered as clickable HTML anchors
- Displayed as backlinks on target documents
### ID-Based Routing
URLs use document IDs instead of slugs:
```
/documents/123 (instead of /documents/my-document-slug)
```
Benefits:
- Guaranteed uniqueness
- Title changes don't break URLs
- Simpler route model binding
### Folder State Persistence
Sidebar folder expanded/collapsed state is stored in `localStorage`:
```javascript
// Managed by Alpine.js
localStorage.getItem('kb_expanded_folders')
// ["Laravel", "Laravel/Livewire", "Docker"]
```
Survives page navigation and browser sessions.
## Customization
### Adding new languages
1. Add to `SetLocale::SUPPORTED_LOCALES` in `app/Http/Middleware/SetLocale.php`
2. Create translation file at `lang/{code}/messages.php`
3. Copy structure from existing language file
### Changing default locale
Edit `config/app.php`:
```php
'locale' => 'en', // Change to your preferred language code
```
### Customizing markdown styles
Edit `resources/css/app.css`:
```css
@layer components {
.prose .wiki-link {
@apply text-indigo-600 hover:text-indigo-800 underline;
}
}
```
## Troubleshooting
### Livewire assets not loading
```bash
docker compose exec php php artisan livewire:publish --assets
```
### Frontend changes not reflecting
```bash
npm run build
docker compose exec php php artisan view:clear
```
### Database connection errors
Check `.env` file matches Docker Compose settings:
```env
DB_CONNECTION=mysql
DB_HOST=kb_mysql
DB_PORT=3306
DB_DATABASE=knowledge_base
DB_USERNAME=kb_user
DB_PASSWORD=kb_password
```
### Alpine.js errors in console
Ensure scripts are loaded in correct order in `knowledge-base.blade.php`:
1. Livewire scripts first
2. Alpine.js initialization (via Vite)
3. Custom Alpine components
## Contributing ## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). Contributions are welcome! Please ensure:
- Code follows Laravel and PSR-12 conventions
## Code of Conduct - All existing tests pass
- New features include tests
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). - UI changes maintain responsive design
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License ## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
## Credits
Built with:
- [Laravel](https://laravel.com) - The PHP Framework
- [Livewire](https://livewire.laravel.com) - Full-stack framework for Laravel
- [Alpine.js](https://alpinejs.dev) - Lightweight JavaScript framework
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS framework
- [EasyMDE](https://github.com/Ionaru/easy-markdown-editor) - Markdown editor
- [league/commonmark](https://commonmark.thephpleague.com) - Markdown parser

View File

@@ -27,34 +27,46 @@
@livewireStyles @livewireStyles
@stack('styles') @stack('styles')
</head> </head>
<body class="font-sans antialiased"> <body class="font-sans antialiased" x-data="{ mobileMenuOpen: false }">
<div class="min-h-screen bg-gray-50"> <div class="min-h-screen bg-gray-50">
<!-- Header --> <!-- Header -->
<header class="bg-white border-b border-gray-200 sticky top-0 z-10"> <header class="bg-white border-b border-gray-200 sticky top-0 z-20">
<div class="max-w-full mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-full mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16"> <div class="flex justify-between h-16">
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<a href="{{ url('/') }}" class="flex items-center space-x-3"> <!-- Mobile Menu Toggle -->
<x-application-logo class="block h-8 w-auto fill-current text-gray-800" /> <button
<h1 class="text-xl font-semibold text-gray-900"> @click="mobileMenuOpen = !mobileMenuOpen"
class="lg:hidden p-2 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500"
aria-label="Toggle menu"
>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path x-show="!mobileMenuOpen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
<path x-show="mobileMenuOpen" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<a href="{{ url('/') }}" class="flex items-center space-x-2 sm:space-x-3">
<x-application-logo class="block h-6 sm:h-8 w-auto fill-current text-gray-800" />
<h1 class="text-lg sm:text-xl font-semibold text-gray-900 hidden xs:block">
{{ config('app.name', 'Knowledge Base') }} {{ config('app.name', 'Knowledge Base') }}
</h1> </h1>
</a> </a>
</div> </div>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-2 sm:space-x-4">
<!-- Quick Switcher Trigger --> <!-- Quick Switcher Trigger -->
<button <button
type="button" type="button"
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" class="inline-flex items-center px-2 sm:px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
x-data x-data
@click.prevent="$dispatch('open-quick-switcher')" @click.prevent="$dispatch('open-quick-switcher')"
> >
<svg class="h-4 w-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="h-4 w-4 sm:mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg> </svg>
{{ __('messages.quick_switcher.title') }} <span class="hidden sm:inline">{{ __('messages.quick_switcher.title') }}</span>
<kbd class="ml-2 px-2 py-1 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded"> <kbd class="hidden md:inline-flex ml-2 px-2 py-1 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded">
Ctrl+K Ctrl+K
</kbd> </kbd>
</button> </button>
@@ -63,10 +75,10 @@ class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-
<div x-data="{ open: false }" @click.away="open = false" class="relative"> <div x-data="{ open: false }" @click.away="open = false" class="relative">
<button <button
@click="open = !open" @click="open = !open"
class="flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none" class="flex items-center px-2 sm:px-3 py-2 text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none"
title="{{ __('messages.settings.change_language') }}" title="{{ __('messages.settings.change_language') }}"
> >
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 sm:w-5 sm:h-5 sm:mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
</svg> </svg>
@php @php
@@ -74,7 +86,7 @@ class="flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:text-
$locales = \App\Http\Middleware\SetLocale::SUPPORTED_LOCALES; $locales = \App\Http\Middleware\SetLocale::SUPPORTED_LOCALES;
@endphp @endphp
<span class="hidden sm:inline">{{ $locales[$currentLocale] ?? 'English' }}</span> <span class="hidden sm:inline">{{ $locales[$currentLocale] ?? 'English' }}</span>
<svg class="ml-1 h-4 w-4" fill="currentColor" viewBox="0 0 20 20"> <svg class="ml-1 h-4 w-4 hidden sm:block" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg> </svg>
</button> </button>
@@ -82,7 +94,7 @@ class="flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:text-
<div <div
x-show="open" x-show="open"
x-transition x-transition
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5 max-h-96 overflow-y-auto" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5 max-h-96 overflow-y-auto z-50"
> >
@foreach($locales as $code => $name) @foreach($locales as $code => $name)
<form method="POST" action="{{ route('locale.update') }}" class="inline-block w-full"> <form method="POST" action="{{ route('locale.update') }}" class="inline-block w-full">
@@ -111,8 +123,11 @@ class="w-full text-left px-4 py-2 text-sm hover:bg-gray-100 {{ $currentLocale ==
@click="open = !open" @click="open = !open"
class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none" class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-900 focus:outline-none"
> >
{{ Auth::user()->name }} <span class="hidden md:inline">{{ Auth::user()->name }}</span>
<svg class="ml-1 h-4 w-4" fill="currentColor" viewBox="0 0 20 20"> <span class="md:hidden w-8 h-8 bg-indigo-100 rounded-full flex items-center justify-center text-indigo-700 font-semibold">
{{ strtoupper(substr(Auth::user()->name, 0, 1)) }}
</span>
<svg class="ml-1 h-4 w-4 hidden md:block" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg> </svg>
</button> </button>
@@ -120,7 +135,7 @@ class="flex items-center text-sm font-medium text-gray-700 hover:text-gray-900 f
<div <div
x-show="open" x-show="open"
x-transition x-transition
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5" class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5 z-50"
> >
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"> <a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
{{ __('messages.nav.profile') }} {{ __('messages.nav.profile') }}
@@ -144,9 +159,14 @@ class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring
</div> </div>
</div> </div>
@else @else
<a href="{{ route('login') }}" class="text-sm text-gray-700 hover:text-gray-900"> <a href="{{ route('login') }}" class="text-sm text-gray-700 hover:text-gray-900 hidden sm:block">
{{ __('messages.nav.login') }} {{ __('messages.nav.login') }}
</a> </a>
<a href="{{ route('login') }}" class="sm:hidden p-2 text-gray-700 hover:bg-gray-100 rounded-md" title="{{ __('messages.nav.login') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
</svg>
</a>
@endauth @endauth
</div> </div>
</div> </div>
@@ -155,8 +175,37 @@ class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring
<!-- Main Content --> <!-- Main Content -->
<div class="flex h-[calc(100vh-4rem)]"> <div class="flex h-[calc(100vh-4rem)]">
<!-- Sidebar --> <!-- Sidebar - Desktop -->
<aside class="w-64 bg-white border-r border-gray-200 overflow-y-auto"> <aside class="hidden lg:block w-64 bg-white border-r border-gray-200 overflow-y-auto">
@livewire('sidebar-tree')
</aside>
<!-- Sidebar - Mobile Overlay -->
<div
x-show="mobileMenuOpen"
@click="mobileMenuOpen = false"
x-transition:enter="transition-opacity ease-linear duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition-opacity ease-linear duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-gray-600 bg-opacity-75 z-30 lg:hidden"
style="display: none;"
></div>
<aside
x-show="mobileMenuOpen"
@click.away="mobileMenuOpen = false"
x-transition:enter="transition ease-in-out duration-300 transform"
x-transition:enter-start="-translate-x-full"
x-transition:enter-end="translate-x-0"
x-transition:leave="transition ease-in-out duration-300 transform"
x-transition:leave-start="translate-x-0"
x-transition:leave-end="-translate-x-full"
class="fixed inset-y-0 left-0 top-16 w-64 bg-white border-r border-gray-200 overflow-y-auto z-40 lg:hidden"
style="display: none;"
>
@livewire('sidebar-tree') @livewire('sidebar-tree')
</aside> </aside>

View File

@@ -1,4 +1,4 @@
<div class="max-w-5xl mx-auto p-8"> <div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8">
<!-- Flash Messages --> <!-- Flash Messages -->
@if (session()->has('message')) @if (session()->has('message'))
<div class="mb-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded"> <div class="mb-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded">
@@ -13,16 +13,16 @@
@endif @endif
<!-- Header --> <!-- Header -->
<div class="mb-6 flex items-center justify-between"> <div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h1 class="text-3xl font-bold text-gray-900"> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900">
{{ $isEditMode ? __('messages.documents.edit_document') : __('messages.documents.new_document') }} {{ $isEditMode ? __('messages.documents.edit_document') : __('messages.documents.new_document') }}
</h1> </h1>
<div class="flex space-x-3"> <div class="flex flex-wrap gap-2 sm:gap-3">
@if($isEditMode && $document) @if($isEditMode && $document)
<a <a
href="{{ route('documents.show', $document) }}" href="{{ route('documents.show', $document) }}"
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 flex-1 sm:flex-none"
> >
{{ __('messages.common.cancel') }} {{ __('messages.common.cancel') }}
</a> </a>
@@ -30,14 +30,14 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text
<button <button
wire:click="delete" wire:click="delete"
wire:confirm="{{ __('messages.documents.delete_confirm') }}" wire:confirm="{{ __('messages.documents.delete_confirm') }}"
class="inline-flex items-center px-4 py-2 border border-red-300 rounded-md text-sm font-medium text-red-700 bg-white hover:bg-red-50" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 border border-red-300 rounded-md text-sm font-medium text-red-700 bg-white hover:bg-red-50 flex-1 sm:flex-none"
> >
{{ __('messages.documents.delete') }} {{ __('messages.documents.delete') }}
</button> </button>
@else @else
<a <a
href="{{ route('documents.show', 'home') }}" href="{{ route('documents.show', 'home') }}"
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 flex-1 sm:flex-none"
> >
{{ __('messages.common.cancel') }} {{ __('messages.common.cancel') }}
</a> </a>
@@ -45,12 +45,13 @@ class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md text
<button <button
wire:click="save" wire:click="save"
class="inline-flex items-center px-4 py-2 bg-indigo-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-indigo-700" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 bg-indigo-600 border border-transparent rounded-md text-sm font-medium text-white hover:bg-indigo-700 flex-1 sm:flex-none"
> >
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 sm:mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
</svg> </svg>
{{ __('messages.documents.save') }} <span class="hidden sm:inline">{{ __('messages.documents.save') }}</span>
<span class="sm:hidden">{{ __('messages.documents.save') }}</span>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,15 +1,15 @@
<div class="max-w-4xl mx-auto p-8"> <div class="max-w-4xl mx-auto p-4 sm:p-6 lg:p-8">
<!-- Document Header --> <!-- Document Header -->
<div class="mb-8"> <div class="mb-6 sm:mb-8">
<div class="flex items-center justify-between mb-4"> <div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-4">
<h1 class="text-4xl font-bold text-gray-900"> <h1 class="text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900 break-words">
{{ $document->title }} {{ $document->title }}
</h1> </h1>
@auth @auth
<a <a
href="{{ route('documents.edit', $document) }}" href="{{ route('documents.edit', $document) }}"
class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" class="inline-flex items-center justify-center px-3 sm:px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 whitespace-nowrap"
> >
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
@@ -19,7 +19,7 @@ class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-
@endauth @endauth
</div> </div>
<div class="flex items-center text-sm text-gray-500 space-x-4"> <div class="flex flex-col sm:flex-row sm:items-center text-xs sm:text-sm text-gray-500 gap-2 sm:gap-4">
<span> <span>
{{ __('messages.documents.updated') }} {{ $document->updated_at->diffForHumans() }} {{ __('messages.documents.updated') }} {{ $document->updated_at->diffForHumans() }}
</span> </span>
@@ -37,7 +37,7 @@ class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white text-sm font-
</div> </div>
<!-- Document Content --> <!-- Document Content -->
<div class="prose prose-lg max-w-none mb-12"> <div class="prose prose-sm sm:prose-base lg:prose-lg max-w-none mb-8 sm:mb-12">
{!! $renderedContent !!} {!! $renderedContent !!}
</div> </div>
@@ -69,9 +69,9 @@ class="block p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition"
@endif @endif
<!-- Document Metadata --> <!-- Document Metadata -->
<div class="mt-12 pt-8 border-t border-gray-200"> <div class="mt-8 sm:mt-12 pt-6 sm:pt-8 border-t border-gray-200">
<div class="grid grid-cols-2 gap-4 text-sm text-gray-500"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs sm:text-sm text-gray-500">
<div> <div class="break-all">
<span class="font-medium">{{ __('messages.documents.path') }}:</span> <span class="font-medium">{{ __('messages.documents.path') }}:</span>
<code class="ml-2 text-xs bg-gray-100 px-2 py-1 rounded">{{ $document->path }}</code> <code class="ml-2 text-xs bg-gray-100 px-2 py-1 rounded">{{ $document->path }}</code>
</div> </div>

View File

@@ -32,7 +32,7 @@ class="fixed inset-0 z-50 overflow-y-auto"
style="display: none;" style="display: none;"
@click="open = false" @click="open = false"
> >
<div class="flex min-h-full items-start justify-center p-4 pt-[10vh]"> <div class="flex min-h-full items-start justify-center p-2 sm:p-4 pt-[5vh] sm:pt-[10vh]">
<div <div
class="w-full max-w-2xl bg-white rounded-lg shadow-2xl" class="w-full max-w-2xl bg-white rounded-lg shadow-2xl"
@click.stop @click.stop
@@ -41,16 +41,16 @@ class="w-full max-w-2xl bg-white rounded-lg shadow-2xl"
wire:keydown.enter.prevent="selectDocument" wire:keydown.enter.prevent="selectDocument"
> >
<!-- Search Input --> <!-- Search Input -->
<div class="p-4 border-b border-gray-200"> <div class="p-3 sm:p-4 border-b border-gray-200">
<div class="relative"> <div class="relative">
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="absolute left-2 sm:left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 sm:h-5 sm:w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg> </svg>
<input <input
x-ref="searchInput" x-ref="searchInput"
type="text" type="text"
wire:model.live="search" wire:model.live="search"
class="w-full pl-10 pr-4 py-3 border-0 focus:ring-0 text-lg" class="w-full pl-8 sm:pl-10 pr-4 py-2 sm:py-3 border-0 focus:ring-0 text-base sm:text-lg"
placeholder="{{ __('messages.quick_switcher.placeholder') }}" placeholder="{{ __('messages.quick_switcher.placeholder') }}"
autocomplete="off" autocomplete="off"
> >
@@ -58,7 +58,7 @@ class="w-full pl-10 pr-4 py-3 border-0 focus:ring-0 text-lg"
</div> </div>
<!-- Results --> <!-- Results -->
<div class="max-h-96 overflow-y-auto"> <div class="max-h-60 sm:max-h-96 overflow-y-auto">
@if(empty($this->results)) @if(empty($this->results))
<div class="p-8 text-center text-gray-500"> <div class="p-8 text-center text-gray-500">
{{ __('messages.quick_switcher.no_results') }} {{ __('messages.quick_switcher.no_results') }}
@@ -102,21 +102,21 @@ class="block px-4 py-3 hover:bg-gray-50 transition {{ $index === $selectedIndex
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="px-4 py-3 bg-gray-50 border-t border-gray-200 text-xs text-gray-500"> <div class="px-3 sm:px-4 py-2 sm:py-3 bg-gray-50 border-t border-gray-200 text-xs text-gray-500">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-2 sm:space-x-4 flex-wrap gap-y-1">
<span class="flex items-center"> <span class="flex items-center">
<kbd class="px-2 py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-1"></kbd> <kbd class="px-1.5 sm:px-2 py-0.5 sm:py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-1"></kbd>
<kbd class="px-2 py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-2"></kbd> <kbd class="px-1.5 sm:px-2 py-0.5 sm:py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-1 sm:mr-2"></kbd>
{{ __('messages.quick_switcher.navigate') }} <span class="hidden sm:inline">{{ __('messages.quick_switcher.navigate') }}</span>
</span> </span>
<span class="flex items-center"> <span class="flex items-center">
<kbd class="px-2 py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-2"></kbd> <kbd class="px-1.5 sm:px-2 py-0.5 sm:py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-1 sm:mr-2"></kbd>
{{ __('messages.quick_switcher.select') }} <span class="hidden sm:inline">{{ __('messages.quick_switcher.select') }}</span>
</span> </span>
<span class="flex items-center"> <span class="flex items-center">
<kbd class="px-2 py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-2">esc</kbd> <kbd class="px-1.5 sm:px-2 py-0.5 sm:py-1 bg-white border border-gray-300 rounded text-xs font-semibold mr-1 sm:mr-2">esc</kbd>
{{ __('messages.quick_switcher.close') }} <span class="hidden sm:inline">{{ __('messages.quick_switcher.close') }}</span>
</span> </span>
</div> </div>
</div> </div>