diff --git a/app/Containers/Dashboard/Actions/ProcessMixedFilesAction.php b/app/Containers/Dashboard/Actions/ProcessMixedFilesAction.php index 26fb0bf..6dddd54 100644 --- a/app/Containers/Dashboard/Actions/ProcessMixedFilesAction.php +++ b/app/Containers/Dashboard/Actions/ProcessMixedFilesAction.php @@ -4,27 +4,21 @@ namespace App\Containers\Dashboard\Actions; use App\Containers\Article\Models\Category; use App\Containers\Article\Models\Post; -use App\Containers\Dashboard\Tasks\CallAiServiceForFileSelectionTask; use App\Containers\Dashboard\Tasks\CallAiServiceTask; use App\Containers\Dashboard\Tasks\CreatePostFromAiDataTask; use App\Containers\Dashboard\Tasks\ExtractTextFromDocumentTask; -use App\Containers\Dashboard\Tasks\ExtractTextFragmentTask; use App\Containers\Dashboard\Tasks\FindMainNewsFileTask; -use App\Containers\Dashboard\Tasks\UnpackArchiveTask; use Illuminate\Http\UploadedFile; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; class ProcessMixedFilesAction { - private array $extractPathsToCleanup = []; - public function __construct( private readonly FindMainNewsFileTask $findMainNewsFileTask, private readonly ExtractTextFromDocumentTask $extractTextFromDocumentTask, private readonly CallAiServiceTask $callAiServiceTask, private readonly CreatePostFromAiDataTask $createPostFromAiDataTask, - private readonly UnpackArchiveTask $unpackArchiveTask, ) {} /** @@ -40,25 +34,6 @@ class ProcessMixedFilesAction 'files' => $files->map(fn($f) => $f->getClientOriginalName())->toArray(), ]); - // Проверяем, есть ли архивы и нужно ли их распаковывать - $files = $this->processArchives($files); - - Log::info('[ProcessMixedFilesAction] Файлы после обработки архивов', [ - 'files_count' => $files->count(), - 'files' => $files->map(fn($f) => $f->getClientOriginalName())->toArray(), - ]); - - // После распаковки проверяем, что есть DOC/DOCX файл - $hasDocOrDocx = $files->contains(function ($file) { - $ext = strtolower($file->getClientOriginalExtension()); - return in_array($ext, ['doc', 'docx']); - }); - - if (!$hasDocOrDocx) { - Log::error('[ProcessMixedFilesAction] После распаковки не найден DOC/DOCX файл'); - throw new \RuntimeException('В архиве не найден DOC или DOCX файл для извлечения текста новости.'); - } - // Находим основной файл с текстом новости $mainFile = $this->findMainNewsFileTask->run($files); @@ -120,9 +95,6 @@ class ProcessMixedFilesAction // Создаём пост $post = $this->createPostFromAiDataTask->run($newsData, $documentPath, $mediaPaths, $attachedFiles); - // Очищаем временные директории после успешной обработки - $this->cleanupExtractPaths(); - // Возвращаем данные для отображения return $this->prepareResponse($post, $newsData); } @@ -147,81 +119,13 @@ class ProcessMixedFilesAction ]; } - /** - * Обрабатывает архивы: распаковывает, если загружены только архивы - */ - private function processArchives(Collection $files): Collection - { - $archiveExtensions = ['zip']; - - // Убедимся, что директория для распаковки существует - $tempPath = storage_path('app/temp'); - if (!file_exists($tempPath)) { - mkdir($tempPath, 0755, true); - } - - // Разделяем файлы на архивы и остальные - $archives = $files->filter(fn($file) => - in_array(strtolower($file->getClientOriginalExtension()), $archiveExtensions) - ); - - $nonArchives = $files->filter(fn($file) => - !in_array(strtolower($file->getClientOriginalExtension()), $archiveExtensions) - ); - - // Если есть только архивы (нет других файлов) — распаковываем - if ($archives->isNotEmpty() && $nonArchives->isEmpty()) { - Log::info('[ProcessMixedFilesAction] Обнаружены только архивы, начинаем распаковку', [ - 'archives_count' => $archives->count(), - ]); - - $unpackedFiles = collect(); - $extractPaths = []; - - foreach ($archives as $archive) { - try { - $result = $this->unpackArchiveTask->run($archive); - $extractedFiles = $result['files']; - $extractPaths[] = $result['extract_path']; - - $unpackedFiles = $unpackedFiles->merge($extractedFiles); - - Log::info('[ProcessMixedFilesAction] Архив распакован', [ - 'archive' => $archive->getClientOriginalName(), - 'extracted_count' => $extractedFiles->count(), - ]); - } catch (\Exception $e) { - Log::error('[ProcessMixedFilesAction] Ошибка при распаковке архива', [ - 'archive' => $archive->getClientOriginalName(), - 'error' => $e->getMessage(), - ]); - throw $e; - } - } - - // Сохраняем пути для очистки после обработки - $this->extractPathsToCleanup = $extractPaths; - - return $unpackedFiles; - } - - // Если есть не-архивные файлы — возвращаем все файлы как есть - // (архивы будут обработаны как обычные медиа-файлы) - Log::info('[ProcessMixedFilesAction] Обнаружены смешанные файлы, архивы не распаковываем', [ - 'archives_count' => $archives->count(), - 'non_archives_count' => $nonArchives->count(), - ]); - - return $files; - } - /** * Обрабатывает прикреплённые файлы (для добавления в контент) */ private function processAttachedFiles(Collection $files, UploadedFile $mainFile): array { $attachedFiles = []; - $fileExtensions = ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar']; + $fileExtensions = ['doc', 'docx', 'pdf', 'xls', 'xlsx', 'ppt', 'pptx']; foreach ($files as $file) { // Пропускаем основной файл @@ -285,49 +189,4 @@ class ProcessMixedFilesAction return round($bytes, 2) . ' ' . $units[$pow]; } - - /** - * Очищает временные директории после распаковки - */ - private function cleanupExtractPaths(): void - { - foreach ($this->extractPathsToCleanup as $path) { - if (file_exists($path)) { - $this->cleanupDirectory($path); - } - } - $this->extractPathsToCleanup = []; - } - - /** - * Очищает директорию рекурсивно - */ - private function cleanupDirectory(string $path): void - { - if (!file_exists($path) || !is_dir($path)) { - return; - } - - try { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); - } elseif ($file->isDir()) { - @rmdir($file->getPathname()); - } - } - - @rmdir($path); - } catch (\Exception $e) { - Log::warning('[ProcessMixedFilesAction] Не удалось очистить временную директорию', [ - 'error' => $e->getMessage(), - 'path' => $path, - ]); - } - } } diff --git a/app/Containers/Dashboard/Tasks/GetDraftPostsTask.php b/app/Containers/Dashboard/Tasks/GetAiPreparedPostsTask.php similarity index 80% rename from app/Containers/Dashboard/Tasks/GetDraftPostsTask.php rename to app/Containers/Dashboard/Tasks/GetAiPreparedPostsTask.php index 7d67da6..b6af83d 100644 --- a/app/Containers/Dashboard/Tasks/GetDraftPostsTask.php +++ b/app/Containers/Dashboard/Tasks/GetAiPreparedPostsTask.php @@ -2,16 +2,17 @@ namespace App\Containers\Dashboard\Tasks; +use App\Containers\Article\Enums\PostStatus; use App\Containers\Article\Models\Post; use Illuminate\Database\Eloquent\Collection; -class GetDraftPostsTask +class GetAiPreparedPostsTask { public function run(): Collection { return Post::query() ->with(['category', 'author']) - ->whereNull('publish_at') + ->whereIn('status', [PostStatus::VERIFICATION->value, PostStatus::REJECTED->value]) ->orderBy('created_at', 'desc') ->get([ 'id', diff --git a/app/Containers/Dashboard/Tasks/UnpackArchiveTask.php b/app/Containers/Dashboard/Tasks/UnpackArchiveTask.php deleted file mode 100644 index 1dca3b1..0000000 --- a/app/Containers/Dashboard/Tasks/UnpackArchiveTask.php +++ /dev/null @@ -1,303 +0,0 @@ -getClientOriginalExtension()); - - // Поддерживаем только zip для начала - if ($extension !== 'zip') { - throw new \Exception("Неподдерживаемый формат архива: {$extension}. Поддерживается только ZIP."); - } - - $extractPath = $extractPath ?? storage_path('app/temp/unpacked_' . uniqid()); - - // Создаем директорию если не существует - if (!file_exists($extractPath)) { - mkdir($extractPath, 0755, true); - } - - Log::info('[UnpackArchiveTask] Начало распаковки архива', [ - 'file' => $archive->getClientOriginalName(), - 'size' => $archive->getSize(), - 'extract_path' => $extractPath, - ]); - - try { - $zip = new \ZipArchive(); - - $openResult = $zip->open($archive->getRealPath()); - if ($openResult !== true) { - throw new \Exception('Не удалось открыть архив. Код ошибки: ' . $openResult); - } - - // Извлекаем все файлы с переименованием (кириллица → транслит) - Log::info('[UnpackArchiveTask] Количество файлов в архиве', ['numFiles' => $zip->numFiles]); - - for ($i = 0; $i < $zip->numFiles; $i++) { - $fileInfo = $zip->statIndex($i); - $originalName = $fileInfo['name']; - - Log::info('[UnpackArchiveTask] Обработка файла в архиве', [ - 'index' => $i, - 'original_name' => $originalName, - 'is_dir' => substr($originalName, -1) === '/', - ]); - - // Пропускаем директории - if (substr($originalName, -1) === '/') { - continue; - } - - // Преобразуем имя файла (кириллица → транслит) - $safeName = $this->transliterateFilename($originalName); - - Log::info('[UnpackArchiveTask] Транслитерация имени', [ - 'original' => $originalName, - 'safe' => $safeName, - ]); - - // Определяем директорию для файла - $dirname = dirname($safeName); - $targetDir = $extractPath; - if ($dirname !== '.' && $dirname !== '/') { - $targetDir = $extractPath . '/' . $dirname; - // Создаем поддиректории если нужно - if (!file_exists($targetDir)) { - mkdir($targetDir, 0755, true); - } - } - - // Получаем содержимое файла из архива - $content = $zip->getFromIndex($i); - - if ($content === false) { - Log::error('[UnpackArchiveTask] Не удалось прочитать содержимое файла', [ - 'name' => $originalName, - ]); - continue; - } - - // Сохраняем файл с безопасным именем - $targetPath = $targetDir . '/' . basename($safeName); - $writeResult = file_put_contents($targetPath, $content); - - if ($writeResult === false) { - Log::error('[UnpackArchiveTask] Не удалось записать файл', [ - 'path' => $targetPath, - ]); - continue; - } - - Log::info('[UnpackArchiveTask] Файл извлечен', [ - 'path' => $targetPath, - 'size' => $writeResult, - ]); - } - - $zip->close(); - - Log::info('[UnpackArchiveTask] Архив распакован', [ - 'files_count' => $this->countFilesInDirectory($extractPath), - ]); - - // Собираем все файлы из распакованной директории - $files = $this->collectFilesFromDirectory($extractPath); - - Log::info('[UnpackArchiveTask] Файлы собраны', [ - 'files' => $files->map(fn($f) => $f->getClientOriginalName())->toArray(), - ]); - - // Возвращаем файлы вместе с путем к директории для последующей очистки - return ['files' => $files, 'extract_path' => $extractPath]; - } catch (\Exception $e) { - Log::error('[UnpackArchiveTask] Ошибка при распаковке архива', [ - 'error' => $e->getMessage(), - 'file' => $archive->getClientOriginalName(), - ]); - - // Очищаем временную директорию при ошибке - if (file_exists($extractPath)) { - $this->cleanupDirectory($extractPath); - } - - throw new \Exception('Ошибка при распаковке архива: ' . $e->getMessage()); - } - } - - /** - * Преобразует имя файла (транслитерация кириллицы) - */ - private function transliterateFilename(string $filename): string - { - $translit = [ - 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', - 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', - 'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', - 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', - 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'ch', - 'ш' => 'sh', 'щ' => 'sch', 'ъ' => '', 'ы' => 'y', 'ь' => '', - 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', - 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', - 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I', - 'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', - 'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', - 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', 'Ч' => 'Ch', - 'Ш' => 'Sh', 'Щ' => 'Sch', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', - 'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya', - ]; - - $pathinfo = pathinfo($filename); - $dirname = $pathinfo['dirname'] ?? ''; - $filename = $pathinfo['filename']; - $extension = $pathinfo['extension'] ?? ''; - - // Транслитерируем имя файла - $transliterated = strtr($filename, $translit); - - // Заменяем пробелы и спецсимволы на подчеркивания - $transliterated = preg_replace('/[^A-Za-z0-9_\-]/', '_', $transliterated); - - // Собираем обратно - $result = $dirname !== '.' ? $dirname . '/' . $transliterated : $transliterated; - if ($extension) { - $result .= '.' . $extension; - } - - return $result; - } - - /** - * Собирает все файлы из директории рекурсивно - */ - private function collectFilesFromDirectory(string $path): Collection - { - $files = collect(); - - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isFile()) { - // Определяем MIME-тип для правильного расширения - $finfo = new \finfo(FILEINFO_MIME_TYPE); - $mimeType = $finfo->file($file->getPathname()); - - // Получаем расширение по MIME-типу - $extension = $this->getExtensionFromMimeType($mimeType); - - // Если не удалось определить по MIME, используем расширение из имени - if (!$extension) { - $extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION); - } - - $uploadedFile = new UploadedFile( - $file->getPathname(), - $file->getFilename(), - $extension, - null, - true - ); - $files->push($uploadedFile); - } - } - - return $files; - } - - /** - * Возвращает расширение по MIME-типу - */ - private function getExtensionFromMimeType(string $mimeType): ?string - { - $map = [ - 'application/msword' => 'doc', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', - 'application/pdf' => 'pdf', - 'application/vnd.ms-excel' => 'xls', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', - 'image/jpeg' => 'jpg', - 'image/png' => 'png', - 'image/webp' => 'webp', - 'image/gif' => 'gif', - 'image/bmp' => 'bmp', - 'application/zip' => 'zip', - ]; - - return $map[$mimeType] ?? null; - } - - /** - * Подсчитывает количество файлов в директории - */ - private function countFilesInDirectory(string $path): int - { - $count = 0; - - if (!is_dir($path)) { - return 0; - } - - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isFile()) { - $count++; - } - } - - return $count; - } - - /** - * Очищает временную директорию - */ - private function cleanupDirectory(string $path): void - { - if (!file_exists($path) || !is_dir($path)) { - return; - } - - try { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($iterator as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); - } elseif ($file->isDir()) { - @rmdir($file->getPathname()); - } - } - - @rmdir($path); - } catch (\Exception $e) { - Log::warning('[UnpackArchiveTask] Не удалось очистить временную директорию', [ - 'error' => $e->getMessage(), - 'path' => $path, - ]); - } - } -} diff --git a/app/Containers/Dashboard/UI/WEB/Controllers/IndexDashboardController.php b/app/Containers/Dashboard/UI/WEB/Controllers/IndexDashboardController.php index 97d7673..83d29fe 100755 --- a/app/Containers/Dashboard/UI/WEB/Controllers/IndexDashboardController.php +++ b/app/Containers/Dashboard/UI/WEB/Controllers/IndexDashboardController.php @@ -2,22 +2,22 @@ namespace App\Containers\Dashboard\UI\WEB\Controllers; -use App\Containers\Dashboard\Tasks\GetDraftPostsTask; +use App\Containers\Dashboard\Tasks\GetAiPreparedPostsTask; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class IndexDashboardController extends Controller { public function __construct( - private readonly GetDraftPostsTask $getDraftPostsTask, + private readonly GetAiPreparedPostsTask $getAiPreparedPostsTask, ) {} public function __invoke(Request $request): \Inertia\Response { - $draftPosts = $this->getDraftPostsTask->run(); + $aiPreparedPosts = $this->getAiPreparedPostsTask->run(); return inertia()->render('Dashboard/Main', [ - 'draftPosts' => $draftPosts, + 'aiPreparedPosts' => $aiPreparedPosts, ]); } } \ No newline at end of file diff --git a/app/Containers/Dashboard/UI/WEB/Requests/StoreFilesRequest.php b/app/Containers/Dashboard/UI/WEB/Requests/StoreFilesRequest.php index 120bf41..1d9f4da 100755 --- a/app/Containers/Dashboard/UI/WEB/Requests/StoreFilesRequest.php +++ b/app/Containers/Dashboard/UI/WEB/Requests/StoreFilesRequest.php @@ -27,10 +27,10 @@ class StoreFilesRequest extends FormRequest 'required', 'file', 'mimes:doc,docx,pdf,xls,xlsx,jpg,jpeg,png,webp,gif,zip', - 'max:20480' // макс 20МБ на КАЖДЫЙ файл + 'max:40960' // макс 40МБ на КАЖДЫЙ файл ], - // Обязательно наличие хотя бы одного DOC или DOCX файла (или архива, который будет распакован) + // Обязательно наличие хотя бы одного DOC или DOCX файла 'files' => [ 'required', 'array', @@ -39,19 +39,8 @@ class StoreFilesRequest extends FormRequest $ext = strtolower($file->getClientOriginalExtension()); return in_array($ext, ['doc', 'docx']); }); - - $hasArchive = collect($value)->contains(function ($file) { - $ext = strtolower($file->getClientOriginalExtension()); - return in_array($ext, ['zip']); - }); - // Если есть только архивы — проверяем их содержимое после распаковки - if (!$hasDocOrDocx && $hasArchive) { - // Разрешаем, проверка будет после распаковки - return; - } - - if (!$hasDocOrDocx && !$hasArchive) { + if (!$hasDocOrDocx) { $fail('Должен быть загружен хотя бы один DOC или DOCX файл для извлечения текста новости.'); } }, @@ -66,8 +55,8 @@ class StoreFilesRequest extends FormRequest 'files.array' => 'Поле должно быть массивом файлов', 'files.min' => 'Загрузите хотя бы один файл', 'files.max' => 'Нельзя загрузить больше 20 файлов за один раз', - 'files.*.mimes' => 'Файл имеет недопустимый формат. Разрешены: DOC, DOCX, PDF, XLS, XLSX, JPG, JPEG, PNG, WEBP, GIF', - 'files.*.max' => 'Размер файла превышает 20 МБ.', + 'files.*.mimes' => 'Файл имеет недопустимый формат. Разрешены: DOC, DOCX, PDF, XLS, XLSX, JPG, JPEG, PNG, WEBP, GIF, ZIP', + 'files.*.max' => 'Размер файла превышает 40 МБ.', ]; } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 29fcd7d..bb45209 100755 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "fslightbox": "^3.4.1", "fslightbox-vue": "^2.2.1", "js-cookie": "^3.0.5", + "jszip": "^3.10.1", "preline": "^1.9.0", "slugify": "^1.6.6", "vite": "^6.3.5", @@ -2043,6 +2044,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -3526,11 +3533,16 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -4058,7 +4070,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -4149,6 +4160,48 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", @@ -4179,6 +4232,15 @@ "vite": "^5.0.0 || ^6.0.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7545,6 +7607,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -7878,6 +7946,12 @@ "@popperjs/core": "^2.11.2" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -8353,6 +8427,12 @@ "node": ">=0.10.0" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -9663,7 +9743,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vary": { diff --git a/package.json b/package.json index 6372e51..29e108a 100755 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "fslightbox": "^3.4.1", "fslightbox-vue": "^2.2.1", "js-cookie": "^3.0.5", + "jszip": "^3.10.1", "preline": "^1.9.0", "slugify": "^1.6.6", "vite": "^6.3.5", diff --git a/resources/js/Pages/Dashboard/Main.vue b/resources/js/Pages/Dashboard/Main.vue index 0ed2fc3..9d4a91b 100755 --- a/resources/js/Pages/Dashboard/Main.vue +++ b/resources/js/Pages/Dashboard/Main.vue @@ -1,368 +1,871 @@ diff --git a/resources/js/mixins/LinksReform.js b/resources/js/mixins/LinksReform.js index ee8b9cb..df753d5 100755 --- a/resources/js/mixins/LinksReform.js +++ b/resources/js/mixins/LinksReform.js @@ -3,7 +3,7 @@ export const linksReform = { data() { return { - excludedPaths: ['/sveden/', '/storage/', '/upload/', '/panorama/', '/abitur/'] + excludedPaths: ['/sveden/', '/storage/', '/upload/', '/panorama/', '/abitur/', '/admin/'] } }, methods: {