bianjunhui 1 هفته پیش
والد
کامیت
764cf93033

+ 1 - 1
packages/Longyi/ImageUpload/src/Config/image_upload.php

@@ -47,7 +47,7 @@ return [
     | The directory where images will be stored.
     |
     */
-    'upload_directory' => 'images/uploads',
+    'upload_directory' => 'uploads',
 
     /*
     |--------------------------------------------------------------------------

+ 174 - 0
packages/Longyi/ImageUpload/src/Http/Controllers/Admin/ImageUploadController.php

@@ -4,6 +4,7 @@ namespace Longyi\ImageUpload\Http\Controllers\Admin;
 
 use App\Http\Controllers\Controller;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Storage;
 use Longyi\ImageUpload\Services\ImageUploadService;
 use Exception;
 
@@ -163,4 +164,177 @@ class ImageUploadController extends Controller
             ], 422);
         }
     }
+
+    /**
+     * Get folders list.
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function getFolders(Request $request)
+    {
+        try {
+            $disk = config('image_upload.default_disk', 's3');
+            $basePath = config('image_upload.upload_directory', 'images/uploads');
+
+            $folders = [];
+            $storage = Storage::disk($disk);
+
+            // Get all directories under base path
+            $allDirs = $storage->allDirectories($basePath);
+
+            foreach ($allDirs as $dir) {
+                $relativePath = str_replace($basePath . '/', '', $dir);
+                if (! empty($relativePath)) {
+                    $folders[] = [
+                        'path' => $dir,
+                        'name' => $relativePath,
+                    ];
+                }
+            }
+
+            return response()->json([
+                'success' => true,
+                'data' => [
+                    'base_path' => $basePath,
+                    'folders' => $folders,
+                ],
+            ]);
+        } catch (Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => $e->getMessage(),
+            ], 422);
+        }
+    }
+
+    /**
+     * Create a folder.
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function createFolder(Request $request)
+    {
+        try {
+            $request->validate([
+                'folder_name' => 'required|string|max:255',
+                'parent_folder' => 'nullable|string',
+            ]);
+
+            $folderName = $request->input('folder_name');
+            $parentFolder = $request->input('parent_folder', '');
+
+            $disk = config('image_upload.default_disk', 's3');
+            $basePath = config('image_upload.upload_directory', 'uploads');
+            $folderPath = $basePath;
+
+            if (! empty($parentFolder)) {
+                $folderPath .= '/' . trim($parentFolder, '/');
+            }
+            $folderPath .= '/' . trim($folderName, '/');
+            // For S3, create a placeholder file to "create" the folder
+            $storage = Storage::disk($disk);
+
+            if (! $storage->exists($folderPath)) {
+                $storage->put($folderPath . '/.gitkeep', '');
+            }
+
+            return response()->json([
+                'success' => true,
+                'message' => __('Folder created successfully'),
+                'data' => [
+                    'path' => $folderPath,
+                    'name' => trim($folderName, '/'),
+                ],
+            ]);
+        } catch (Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => $e->getMessage(),
+            ], 422);
+        }
+    }
+
+    /**
+     * Delete a folder.
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function deleteFolder(Request $request)
+    {
+        try {
+            $request->validate([
+                'folder_path' => 'required|string',
+            ]);
+
+            $folderPath = $request->input('folder_path');
+            $disk = config('image_upload.default_disk', 's3');
+            $storage = Storage::disk($disk);
+
+            if ($storage->exists($folderPath)) {
+                $storage->deleteDirectory($folderPath);
+            }
+
+            return response()->json([
+                'success' => true,
+                'message' => __('Folder deleted successfully'),
+            ]);
+        } catch (Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => $e->getMessage(),
+            ], 422);
+        }
+    }
+
+    /**
+     * Get images in a folder.
+     *
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function getImages(Request $request)
+    {
+        try {
+            $folderPath = $request->input('folder_path', '');
+            $disk = config('image_upload.default_disk', 's3');
+            $basePath = config('image_upload.upload_directory', 'images/uploads');
+
+            $searchPath = $basePath;
+            if (! empty($folderPath)) {
+                $searchPath .= '/' . trim($folderPath, '/');
+            }
+
+            $storage = Storage::disk($disk);
+            $files = $storage->files($searchPath);
+
+            $images = [];
+            $allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
+
+            foreach ($files as $file) {
+                $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
+                if (in_array($extension, $allowedExtensions)) {
+                    $images[] = [
+                        'path' => $file,
+                        'name' => basename($file),
+                        'url' => $storage->url($file),
+                        'size' => $storage->size($file),
+                        'last_modified' => $storage->lastModified($file),
+                    ];
+                }
+            }
+
+            return response()->json([
+                'success' => true,
+                'data' => $images,
+            ]);
+        } catch (Exception $e) {
+            return response()->json([
+                'success' => false,
+                'message' => $e->getMessage(),
+            ], 422);
+        }
+    }
 }

+ 26 - 0
packages/Longyi/ImageUpload/src/Resources/lang/en/app.php

@@ -21,5 +21,31 @@ return [
             'delete_success' => 'Image deleted successfully',
             'delete_failed' => 'Failed to delete image',
         ],
+        'media-manager' => [
+            'title' => 'Media Manager',
+            'upload_images' => 'Upload Images',
+            'create_folder' => 'Create Folder',
+            'folder_name' => 'Folder Name',
+            'folder_name_placeholder' => 'Enter folder name',
+            'create' => 'Create',
+            'cancel' => 'Cancel',
+            'all_folders' => 'All Folders',
+            'no_folders' => 'No folders yet',
+            'no_images' => 'No images yet',
+            'select' => 'Select',
+            'selected' => 'Selected',
+            'insert' => 'Insert Image',
+            'confirm_delete_folder' => 'Are you sure you want to delete this folder? All images in it will also be deleted.',
+            'confirm_delete_image' => 'Are you sure you want to delete this image?',
+            'folder_created' => 'Folder created successfully',
+            'folder_deleted' => 'Folder deleted successfully',
+            'image_deleted' => 'Image deleted successfully',
+            'upload_success' => 'Upload successful',
+            'drag_or_click' => 'Drag & drop files here or click to upload',
+            'uploading' => 'Uploading...',
+            'loading' => 'Loading...',
+            'root_directory' => 'Root Directory',
+            'back' => 'Back',
+        ],
     ],
 ];

+ 26 - 0
packages/Longyi/ImageUpload/src/Resources/lang/zh_CN/app.php

@@ -21,5 +21,31 @@ return [
             'delete_success' => '图片删除成功',
             'delete_failed' => '图片删除失败',
         ],
+        'media-manager' => [
+            'title' => '媒体管理器',
+            'upload_images' => '上传图片',
+            'create_folder' => '新建文件夹',
+            'folder_name' => '文件夹名称',
+            'folder_name_placeholder' => '请输入文件夹名称',
+            'create' => '创建',
+            'cancel' => '取消',
+            'all_folders' => '全部文件夹',
+            'no_folders' => '暂无文件夹',
+            'no_images' => '暂无图片',
+            'select' => '选择',
+            'selected' => '已选择',
+            'insert' => '插入图片',
+            'confirm_delete_folder' => '确定要删除此文件夹吗?文件夹中的所有图片也将被删除。',
+            'confirm_delete_image' => '确定要删除此图片吗?',
+            'folder_created' => '文件夹创建成功',
+            'folder_deleted' => '文件夹删除成功',
+            'image_deleted' => '图片删除成功',
+            'upload_success' => '上传成功',
+            'drag_or_click' => '拖拽文件到此处或点击上传',
+            'uploading' => '上传中...',
+            'loading' => '加载中...',
+            'root_directory' => '根目录',
+            'back' => '返回',
+        ],
     ],
 ];

+ 515 - 0
packages/Longyi/ImageUpload/src/Resources/views/components/media-manager.blade.php

@@ -0,0 +1,515 @@
+<!-- Media Manager Component for TinyMCE -->
+<script type="text/x-template" id="v-media-manager-template">
+    <div>
+        <!-- Media Manager Modal -->
+        <x-admin::modal ref="mediaManagerModal">
+            <!-- Modal Header -->
+            <x-slot:header>
+                <p class="flex items-center gap-2.5 text-lg font-bold text-gray-800 dark:text-white">
+                    <span class="icon-image text-2xl text-gray-800"></span>
+                    @lang('imageupload::app.admin.media-manager.title')
+                </p>
+            </x-slot>
+
+            <!-- Modal Content -->
+            <x-slot:content>
+                <div class="flex gap-4 h-[500px]">
+                    <!-- Left Sidebar: Folder Tree -->
+                    <div class="w-56 border-r border-gray-200 dark:border-gray-600 pr-4 flex flex-col">
+                        <!-- Create Folder Button -->
+                        <div class="mb-3">
+                            <button
+                                type="button"
+                                class="primary-button w-full text-sm py-1.5"
+                                @click="showCreateFolder = true"
+                            >
+                                <span class="icon-folder text-sm"></span>
+                                @lang('imageupload::app.admin.media-manager.create_folder')
+                            </button>
+                        </div>
+
+                        <!-- Create Folder Input -->
+                        <div v-if="showCreateFolder" class="mb-3">
+                            <div class="flex gap-1">
+                                <input
+                                    type="text"
+                                    v-model="newFolderName"
+                                    placeholder="@lang('imageupload::app.admin.media-manager.folder_name_placeholder')"
+                                    class="flex-1 border border-gray-300 dark:border-gray-600 rounded px-2 py-1 text-sm dark:bg-gray-700 dark:text-white"
+                                    @keyup.enter="createFolder"
+                                />
+                                <button
+                                    type="button"
+                                    class="primary-button text-xs px-2 py-1"
+                                    @click="createFolder"
+                                    :disabled="!newFolderName.trim()"
+                                >
+                                    @lang('imageupload::app.admin.media-manager.create')
+                                </button>
+                            </div>
+                            <button
+                                type="button"
+                                class="text-xs text-gray-500 hover:text-gray-700 mt-1"
+                                @click="showCreateFolder = false; newFolderName = ''"
+                            >
+                                @lang('imageupload::app.admin.media-manager.cancel')
+                            </button>
+                        </div>
+
+                        <!-- Folder List -->
+                        <div class="flex-1 overflow-y-auto">
+                            <p class="text-xs font-semibold text-gray-500 uppercase mb-2">
+                                @lang('imageupload::app.admin.media-manager.all_folders')
+                            </p>
+
+                            <!-- Root Folder -->
+                            <div
+                                class="flex items-center justify-between px-2 py-1.5 rounded cursor-pointer text-sm mb-0.5"
+                                :class="currentFolder === '' ? 'bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-200' : 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300'"
+                                @click="selectFolder('')"
+                            >
+                                <span class="flex items-center gap-1.5 truncate">
+                                    <span class="icon-folder text-sm"></span>
+                                    @lang('imageupload::app.admin.media-manager.root_directory')
+                                </span>
+                            </div>
+
+                            <!-- Sub Folders -->
+                            <div
+                                v-for="folder in folders"
+                                :key="folder.path"
+                                class="flex items-center justify-between px-2 py-1.5 rounded cursor-pointer text-sm mb-0.5 group"
+                                :class="currentFolder === folder.name ? 'bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-200' : 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300'"
+                                @click="selectFolder(folder.name)"
+                            >
+                                <span class="flex items-center gap-1.5 truncate flex-1">
+                                    <span class="icon-folder text-sm"></span>
+                                    <span class="truncate">@{{ folder.name }}</span>
+                                </span>
+                                <button
+                                    type="button"
+                                    class="text-red-500 hover:text-red-700 opacity-0 group-hover:opacity-100 transition-opacity ml-1"
+                                    @click.stop="confirmDeleteFolder(folder)"
+                                    :title="'@lang('imageupload::app.admin.media-manager.delete')'"
+                                >
+                                    <span class="icon-delete text-xs"></span>
+                                </button>
+                            </div>
+
+                            <p v-if="folders.length === 0" class="text-xs text-gray-400 py-2">
+                                @lang('imageupload::app.admin.media-manager.no_folders')
+                            </p>
+                        </div>
+                    </div>
+
+                    <!-- Right Side: Image Browser & Upload -->
+                    <div class="flex-1 flex flex-col">
+                        <!-- Upload Area -->
+                        <div
+                            class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-6 mb-4 text-center cursor-pointer transition-colors hover:border-blue-400"
+                            :class="{ 'border-blue-400 bg-blue-50 dark:bg-blue-900/20': isDragOver }"
+                            @dragover.prevent="isDragOver = true"
+                            @dragleave.prevent="isDragOver = false"
+                            @drop.prevent="handleDrop"
+                            @click="$refs.fileInput.click()"
+                        >
+                            <input
+                                ref="fileInput"
+                                type="file"
+                                accept="image/*"
+                                multiple
+                                class="hidden"
+                                @change="handleFileSelect"
+                            />
+
+                            <div v-if="uploading">
+                                <div class="flex items-center justify-center gap-2 text-blue-600">
+                                    <span class="animate-spin inline-block w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full"></span>
+                                    @lang('imageupload::app.admin.media-manager.uploading')
+                                </div>
+                            </div>
+                            <div v-else>
+                                <span class="icon-upload text-3xl text-gray-400 block mb-2"></span>
+                                <p class="text-sm text-gray-500 dark:text-gray-400">
+                                    @lang('imageupload::app.admin.media-manager.drag_or_click')
+                                </p>
+                            </div>
+                        </div>
+
+                        <!-- Image Grid -->
+                        <div class="flex-1 overflow-y-auto">
+                            <div v-if="loading" class="flex items-center justify-center h-full">
+                                <span class="animate-spin inline-block w-6 h-6 border-2 border-blue-600 border-t-transparent rounded-full"></span>
+                                <span class="ml-2 text-sm text-gray-500">@lang('imageupload::app.admin.media-manager.loading')</span>
+                            </div>
+
+                            <div v-else-if="images.length === 0" class="flex items-center justify-center h-full">
+                                <p class="text-sm text-gray-400">@lang('imageupload::app.admin.media-manager.no_images')</p>
+                            </div>
+
+                            <div v-else class="grid grid-cols-4 gap-3">
+                                <div
+                                    v-for="image in images"
+                                    :key="image.path"
+                                    class="relative group border border-gray-200 dark:border-gray-600 rounded-lg overflow-hidden cursor-pointer aspect-square"
+                                    :class="{ 'ring-2 ring-blue-500': selectedImage && selectedImage.path === image.path }"
+                                    @click="selectImage(image)"
+                                >
+                                    <img
+                                        :src="image.url"
+                                        :alt="image.name"
+                                        class="w-full h-full object-cover"
+                                    />
+                                    <!-- Hover Overlay -->
+                                    <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all flex items-center justify-center">
+                                        <button
+                                            type="button"
+                                            class="text-white bg-red-500 hover:bg-red-600 rounded-full w-7 h-7 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
+                                            @click.stop="confirmDeleteImage(image)"
+                                            :title="'@lang('imageupload::app.admin.media-manager.delete')'"
+                                        >
+                                            <span class="icon-delete text-xs"></span>
+                                        </button>
+                                    </div>
+                                    <!-- Selected Check -->
+                                    <div
+                                        v-if="selectedImage && selectedImage.path === image.path"
+                                        class="absolute top-1 right-1 bg-blue-500 text-white rounded-full w-5 h-5 flex items-center justify-center"
+                                    >
+                                        <span class="text-xs">&#10003;</span>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </x-slot>
+
+            <!-- Modal Footer -->
+            <x-slot:footer>
+                <div class="flex items-center justify-between w-full">
+                    <p v-if="selectedImage" class="text-sm text-gray-600 dark:text-gray-400 truncate max-w-xs">
+                        @lang('imageupload::app.admin.media-manager.selected'): @{{ selectedImage.name }}
+                    </p>
+                    <div class="flex gap-2">
+                        <button
+                            type="button"
+                            class="secondary-button"
+                            @click="close"
+                        >
+                            @lang('imageupload::app.admin.media-manager.cancel')
+                        </button>
+                        <button
+                            type="button"
+                            class="primary-button"
+                            :disabled="!selectedImage"
+                            @click="insertImage"
+                        >
+                            @lang('imageupload::app.admin.media-manager.insert')
+                        </button>
+                    </div>
+                </div>
+            </x-slot>
+        </x-admin::modal>
+    </div>
+</script>
+
+<script type="module">
+    app.component('v-media-manager', {
+        template: '#v-media-manager-template',
+
+        props: {
+            onInsert: {
+                type: Function,
+                default: null,
+            },
+        },
+
+        data() {
+            return {
+                folders: [],
+                images: [],
+                currentFolder: '',
+                selectedImage: null,
+                showCreateFolder: false,
+                newFolderName: '',
+                isDragOver: false,
+                uploading: false,
+                loading: false,
+            };
+        },
+
+        methods: {
+            open() {
+                this.$refs.mediaManagerModal.open();
+                this.loadFolders();
+                this.loadImages();
+            },
+
+            close() {
+                this.$refs.mediaManagerModal.close();
+                this.selectedImage = null;
+                this.showCreateFolder = false;
+                this.newFolderName = '';
+            },
+
+            toggle() {
+                this.$refs.mediaManagerModal.toggle();
+                if (this.$refs.mediaManagerModal.isOpen) {
+                    this.loadFolders();
+                    this.loadImages();
+                }
+            },
+
+            // Load folders from API
+            async loadFolders() {
+                try {
+                    const response = await fetch('{{ route("admin.image_upload.folders") }}', {
+                        headers: {
+                            'Accept': 'application/json',
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                    });
+                    const data = await response.json();
+                    if (data.success) {
+                        this.folders = data.data.folders || [];
+                    }
+                } catch (error) {
+                    console.error('Failed to load folders:', error);
+                }
+            },
+
+            // Load images from current folder
+            async loadImages() {
+                this.loading = true;
+                try {
+                    const url = new URL('{{ route("admin.image_upload.images") }}', window.location.origin);
+                    url.searchParams.set('folder_path', this.currentFolder);
+
+                    const response = await fetch(url.toString(), {
+                        headers: {
+                            'Accept': 'application/json',
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                    });
+                    const data = await response.json();
+                    if (data.success) {
+                        this.images = data.data || [];
+                    }
+                } catch (error) {
+                    console.error('Failed to load images:', error);
+                } finally {
+                    this.loading = false;
+                }
+            },
+
+            // Select a folder
+            selectFolder(folderName) {
+                this.currentFolder = folderName;
+                this.selectedImage = null;
+                this.loadImages();
+            },
+
+            // Create a new folder
+            async createFolder() {
+                if (!this.newFolderName.trim()) return;
+
+                try {
+                    const response = await fetch('{{ route("admin.image_upload.folder.create") }}', {
+                        method: 'POST',
+                        headers: {
+                            'Content-Type': 'application/json',
+                            'Accept': 'application/json',
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                        body: JSON.stringify({
+                            folder_name: this.newFolderName.trim(),
+                            parent_folder: '',
+                        }),
+                    });
+
+                    const data = await response.json();
+                    if (data.success) {
+                        this.$emitter.emit('add-flash', {
+                            type: 'success',
+                            message: '@lang('imageupload::app.admin.media-manager.folder_created')',
+                        });
+                        this.newFolderName = '';
+                        this.showCreateFolder = false;
+                        await this.loadFolders();
+                    } else {
+                        this.$emitter.emit('add-flash', {
+                            type: 'error',
+                            message: data.message || 'Failed to create folder',
+                        });
+                    }
+                } catch (error) {
+                    console.error('Failed to create folder:', error);
+                }
+            },
+
+            // Confirm delete folder
+            confirmDeleteFolder(folder) {
+                if (!confirm('@lang('imageupload::app.admin.media-manager.confirm_delete_folder')')) {
+                    return;
+                }
+                this.deleteFolder(folder);
+            },
+
+            // Delete folder
+            async deleteFolder(folder) {
+                try {
+                    const response = await fetch('{{ route("admin.image_upload.folder.delete") }}', {
+                        method: 'DELETE',
+                        headers: {
+                            'Content-Type': 'application/json',
+                            'Accept': 'application/json',
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                        body: JSON.stringify({
+                            folder_path: folder.path,
+                        }),
+                    });
+
+                    const data = await response.json();
+                    if (data.success) {
+                        this.$emitter.emit('add-flash', {
+                            type: 'success',
+                            message: '@lang('imageupload::app.admin.media-manager.folder_deleted')',
+                        });
+
+                        if (this.currentFolder === folder.name) {
+                            this.currentFolder = '';
+                        }
+                        await this.loadFolders();
+                        await this.loadImages();
+                    }
+                } catch (error) {
+                    console.error('Failed to delete folder:', error);
+                }
+            },
+
+            // Select an image
+            selectImage(image) {
+                if (this.selectedImage && this.selectedImage.path === image.path) {
+                    this.selectedImage = null;
+                } else {
+                    this.selectedImage = image;
+                }
+            },
+
+            // Confirm delete image
+            confirmDeleteImage(image) {
+                if (!confirm('@lang('imageupload::app.admin.media-manager.confirm_delete_image')')) {
+                    return;
+                }
+                this.deleteImage(image);
+            },
+
+            // Delete image
+            async deleteImage(image) {
+                try {
+                    const response = await fetch('{{ route("admin.image_upload.delete") }}', {
+                        method: 'DELETE',
+                        headers: {
+                            'Content-Type': 'application/json',
+                            'Accept': 'application/json',
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                        body: JSON.stringify({
+                            path: image.path,
+                        }),
+                    });
+
+                    const data = await response.json();
+                    if (data.success) {
+                        this.$emitter.emit('add-flash', {
+                            type: 'success',
+                            message: '@lang('imageupload::app.admin.media-manager.image_deleted')',
+                        });
+
+                        if (this.selectedImage && this.selectedImage.path === image.path) {
+                            this.selectedImage = null;
+                        }
+                        await this.loadImages();
+                    }
+                } catch (error) {
+                    console.error('Failed to delete image:', error);
+                }
+            },
+
+            // Handle file drop
+            handleDrop(event) {
+                this.isDragOver = false;
+                const files = Array.from(event.dataTransfer.files);
+                this.uploadFiles(files);
+            },
+
+            // Handle file select
+            handleFileSelect(event) {
+                const files = Array.from(event.target.files);
+                this.uploadFiles(files);
+                event.target.value = '';
+            },
+
+            // Upload files
+            async uploadFiles(files) {
+                if (files.length === 0) return;
+
+                this.uploading = true;
+
+                try {
+                    const formData = new FormData();
+                    files.forEach(file => {
+                        formData.append('images[]', file);
+                    });
+
+                    if (this.currentFolder) {
+                        formData.append('directory', this.currentFolder);
+                    }
+
+                    const response = await fetch('{{ route("admin.image_upload.upload_multiple") }}', {
+                        method: 'POST',
+                        body: formData,
+                        headers: {
+                            'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                        },
+                    });
+
+                    const data = await response.json();
+                    if (data.success) {
+                        this.$emitter.emit('add-flash', {
+                            type: 'success',
+                            message: '@lang('imageupload::app.admin.media-manager.upload_success')',
+                        });
+                        await this.loadImages();
+                    } else {
+                        this.$emitter.emit('add-flash', {
+                            type: 'error',
+                            message: data.message || 'Upload failed',
+                        });
+                    }
+                } catch (error) {
+                    console.error('Upload failed:', error);
+                    this.$emitter.emit('add-flash', {
+                        type: 'error',
+                        message: 'Upload failed',
+                    });
+                } finally {
+                    this.uploading = false;
+                }
+            },
+
+            // Insert selected image into TinyMCE
+            insertImage() {
+                if (!this.selectedImage) return;
+
+                if (this.onInsert) {
+                    this.onInsert(this.selectedImage.url);
+                }
+
+                this.close();
+            },
+        },
+    });
+</script>

+ 6 - 0
packages/Longyi/ImageUpload/src/Routes/admin-routes.php

@@ -9,5 +9,11 @@ Route::group(['middleware' => ['web', 'admin'], 'prefix' => config('app.admin_pa
         Route::post('upload-multiple', 'uploadMultiple')->name('admin.image_upload.upload_multiple');
         Route::delete('delete', 'delete')->name('admin.image_upload.delete');
         Route::get('url', 'getUrl')->name('admin.image_upload.get_url');
+
+        // Folder management routes
+        Route::get('folders', 'getFolders')->name('admin.image_upload.folders');
+        Route::post('folder/create', 'createFolder')->name('admin.image_upload.folder.create');
+        Route::delete('folder/delete', 'deleteFolder')->name('admin.image_upload.folder.delete');
+        Route::get('images', 'getImages')->name('admin.image_upload.images');
     });
 });

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 233 - 198
packages/Webkul/Admin/src/Resources/views/components/tinymce/index.blade.php