diff --git a/src/app/Http/Controllers/ImageUploadController.php b/src/app/Http/Controllers/ImageUploadController.php new file mode 100644 index 0000000..822f9a5 --- /dev/null +++ b/src/app/Http/Controllers/ImageUploadController.php @@ -0,0 +1,54 @@ +validate([ + 'image' => [ + 'required', + 'file', + 'mimes:jpeg,jpg,png,gif,webp', + 'max:2048', // 2MB + ], + ]); + + $file = $request->file('image'); + + // Get original filename without extension for alt text + $originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); + + // Generate unique filename: YYYY/MM/uuid.extension + $year = date('Y'); + $month = date('m'); + $extension = $file->getClientOriginalExtension(); + $filename = Str::uuid() . '.' . $extension; + $path = "images/{$year}/{$month}/{$filename}"; + + // Store to public disk + Storage::disk('public')->putFileAs( + "images/{$year}/{$month}", + $file, + $filename + ); + + // Return URL for EasyMDE (use APP_URL) + $url = asset('storage/' . $path); + + return response()->json([ + 'data' => [ + 'filePath' => $url, + 'altText' => $originalName, + ], + ]); + } +} diff --git a/src/resources/views/livewire/document-editor.blade.php b/src/resources/views/livewire/document-editor.blade.php index eece6f5..5b1bbe9 100644 --- a/src/resources/views/livewire/document-editor.blade.php +++ b/src/resources/views/livewire/document-editor.blade.php @@ -147,6 +147,42 @@ class="w-full" 'guide' ], status: ['lines', 'words', 'cursor'], + // Image upload configuration + uploadImage: true, + imageMaxSize: 2 * 1024 * 1024, // 2MB + imageAccept: 'image/png, image/jpeg, image/gif, image/webp', + imageUploadFunction: (file, onSuccess, onError) => { + const formData = new FormData(); + formData.append('image', file); + + fetch('{{ route("images.upload") }}', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, + 'Accept': 'application/json', + }, + body: formData, + }) + .then(response => { + if (!response.ok) { + return response.json().then(data => { + throw new Error(data.message || 'Upload failed'); + }); + } + return response.json(); + }) + .then(data => { + // Insert markdown with alt text directly + const cm = this.editor.codemirror; + const altText = data.data.altText || 'image'; + const url = data.data.filePath; + const markdown = `![${altText}](${url})`; + cm.replaceSelection(markdown); + }) + .catch(error => { + onError(error.message || 'Failed to upload image'); + }); + }, }); this.editor.codemirror.on('change', () => { diff --git a/src/routes/web.php b/src/routes/web.php index 86afa5e..afdbd37 100644 --- a/src/routes/web.php +++ b/src/routes/web.php @@ -2,6 +2,7 @@ use App\Http\Controllers\ProfileController; use App\Http\Controllers\LocaleController; +use App\Http\Controllers\ImageUploadController; use App\Http\Controllers\Admin\UserController as AdminUserController; use App\Livewire\DocumentViewer; use App\Livewire\DocumentEditor; @@ -29,6 +30,9 @@ Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); + + // Image upload for editor + Route::post('/images/upload', [ImageUploadController::class, 'upload'])->name('images.upload'); }); // Admin routes