From 69657ed7500e6dd2ffd74e6228ba51f1f41dcf93 Mon Sep 17 00:00:00 2001 From: Thorsten Rinne Date: Wed, 1 Jan 2025 10:48:47 +0100 Subject: [PATCH] feat(editor): removed TinyMCE, added Jodit Editor (#3310) Missing: phpMyFAQ plugin --- CHANGELOG.md | 3 +- package.json | 6 +- .../admin/assets/scss/layout/_editor.scss | 24 +- phpmyfaq/admin/assets/src/content/editor.js | 438 +++++++++--------- phpmyfaq/admin/assets/src/content/news.js | 6 +- .../admin/content/media.browser.twig | 39 -- phpmyfaq/src/admin-api-routes.php | 7 + phpmyfaq/src/admin-routes.php | 6 - .../Administration/Api/ImageController.php | 109 +++-- .../Api/MediaBrowserController.php | 114 +++++ .../Administration/MediaBrowserController.php | 68 --- pnpm-lock.yaml | 258 ++++++----- vite.config.js | 6 - 13 files changed, 554 insertions(+), 530 deletions(-) delete mode 100644 phpmyfaq/assets/templates/admin/content/media.browser.twig create mode 100644 phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php delete mode 100644 phpmyfaq/src/phpMyFAQ/Controller/Administration/MediaBrowserController.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc177c837..b52153f8a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ This is a log of major user-visible changes in each phpMyFAQ release. ### phpMyFAQ v4.1.0-dev - unreleased - added configuration to edit robots.txt (Thorsten) -- WIP: added Symfony Routing for administration backend (Thorsten) +- added Symfony Routing for administration backend (Thorsten) +- WIP: migrated from WYSIWYG editor from TinyMCE to Jodit Editor (Thorsten) - removed Webpack, now using Vite v6 (Thorsten) - migrated from Jest to vitest (Thorsten) diff --git a/package.json b/package.json index c194c5a20d..8372870f98 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,10 @@ "bootstrap-icons": "^1.11.3", "chart.js": "^4.4.7", "handlebars": "4.7.8", - "highlight.js": "^11.11.0", + "highlight.js": "^11.11.1", + "jodit": "^4.2.47", "masonry-layout": "^4.2.2", "sortablejs": "^1.15.6", - "tinymce": "^6.8.5", "vanilla-cookieconsent": "^3.0.1" }, "devDependencies": { @@ -61,7 +61,7 @@ "rollup-plugin-sbom": "^1.1.1", "sass": "^1.83.0", "sigmund": "^1.0.1", - "vite": "^6.0.5", + "vite": "^6.0.6", "vite-plugin-compression": "^0.5.1", "vite-plugin-html": "^3.2.2", "vite-plugin-minify": "^2.1.0", diff --git a/phpmyfaq/admin/assets/scss/layout/_editor.scss b/phpmyfaq/admin/assets/scss/layout/_editor.scss index 1550f80a07..74aa73c4eb 100644 --- a/phpmyfaq/admin/assets/scss/layout/_editor.scss +++ b/phpmyfaq/admin/assets/scss/layout/_editor.scss @@ -1,26 +1,4 @@ -// -// TinyMCE -// - -label.mce-label { - max-width: none; -} - -.mce-file { - cursor: pointer; - line-height: 36px; - - &:hover { - text-decoration: underline; - } -} - -.mce-file-preview { - border: 1px solid $gray-900; - margin-right: 16px; - max-height: 32px; - max-width: 32px; -} +@import 'jodit/es2015/jodit.min.css'; .truncate-question { display: inline-block; diff --git a/phpmyfaq/admin/assets/src/content/editor.js b/phpmyfaq/admin/assets/src/content/editor.js index 93e894db16..8d3a68f5fe 100644 --- a/phpmyfaq/admin/assets/src/content/editor.js +++ b/phpmyfaq/admin/assets/src/content/editor.js @@ -13,236 +13,222 @@ * @since 2022-01-31 */ -/* Import TinyMCE */ -import tinymce from 'tinymce'; - -/* Default icons are required. After that, import custom icons if applicable */ -import 'tinymce/icons/default/icons.min.js'; - -/* Required TinyMCE components */ -import 'tinymce/themes/silver/theme.min.js'; -import 'tinymce/models/dom/model.min.js'; - -/* Import a skin (can be a custom skin instead of the default) */ -import 'tinymce/skins/ui/oxide/skin.js'; - -// importing the plugin js. -import 'tinymce/plugins/advlist'; -import 'tinymce/plugins/anchor'; -import 'tinymce/plugins/autolink'; -import 'tinymce/plugins/link'; -import 'tinymce/plugins/image'; -import 'tinymce/plugins/lists'; -import 'tinymce/plugins/charmap'; -import 'tinymce/plugins/searchreplace'; -import 'tinymce/plugins/wordcount'; -import 'tinymce/plugins/code'; -import 'tinymce/plugins/insertdatetime'; -import 'tinymce/plugins/media'; -import 'tinymce/plugins/nonbreaking'; -import 'tinymce/plugins/table'; -import 'tinymce/plugins/help'; -import 'tinymce/plugins/preview'; -import 'tinymce/plugins/visualblocks'; -import 'tinymce/plugins/pagebreak'; -import 'tinymce/plugins/visualchars'; -import 'tinymce/plugins/codesample'; -import 'tinymce/plugins/save'; -import 'tinymce/plugins/directionality'; -import 'tinymce/plugins/emoticons'; -import 'tinymce/plugins/emoticons/js/emojis'; -import 'tinymce/plugins/fullscreen'; - -/* content UI CSS is required */ -import contentUiSkinCss from 'tinymce/skins/ui/oxide/content.js'; - -/* The default content CSS can be changed or replaced with appropriate CSS for the editor content. */ -import contentCss from 'tinymce/skins/content/default/content.js'; +import { Jodit } from 'jodit'; +import 'jodit/esm/plugins/class-span/class-span.js'; +import 'jodit/esm/plugins/clean-html/clean-html.js'; +import 'jodit/esm/plugins/clipboard/clipboard.js'; +import 'jodit/esm/plugins/copy-format/copy-format.js'; +import 'jodit/esm/plugins/delete/delete.js'; +import 'jodit/esm/plugins/fullsize/fullsize.js'; +import 'jodit/esm/plugins/hr/hr.js'; +import 'jodit/esm/plugins/image/image.js'; +import 'jodit/esm/plugins/image-processor/image-processor.js'; +import 'jodit/esm/plugins/image-properties/image-properties.js'; +import 'jodit/esm/plugins/indent/indent.js'; +import 'jodit/esm/plugins/justify/justify.js'; +import 'jodit/esm/plugins/line-height/line-height.js'; +import 'jodit/esm/plugins/media/media.js'; +import 'jodit/esm/plugins/preview/preview.js'; +import 'jodit/esm/plugins/print/print.js'; +import 'jodit/esm/plugins/resizer/resizer.js'; +import 'jodit/esm/plugins/search/search.js'; +import 'jodit/esm/plugins/select/select.js'; +import 'jodit/esm/plugins/source/source.js'; +import 'jodit/esm/plugins/symbols/symbols.js'; +import 'jodit/esm/modules/uploader/uploader.js'; +import 'jodit/esm/plugins/video/video.js'; export const renderEditor = () => { - const form = document.getElementById('faqEditor'); - if (form) { - const editorEnabled = form.getAttribute('data-pmf-enable-editor'); - const editorLanguage = form.getAttribute('data-pmf-editor-language'); - const defaultLanguage = form.getAttribute('data-pmf-default-url'); - - if ('1' !== editorEnabled) { - return; - } - - tinymce.init({ - // General options - language: editorLanguage, - document_base_url: defaultLanguage, - skin_url: 'default', - content_css: 'default', - selector: 'textarea#editor', - relative_urls: false, - convert_urls: false, - remove_linebreaks: false, - use_native_selects: true, - paste_remove_spans: true, - entities: '10', - entity_encoding: 'raw', - height: '50vh', - paste_data_images: true, - visualblocks_default_state: true, - convert_unsafe_embeds: true, - end_container_on_empty_block: true, - extended_valid_elements: 'code[class],video[*],audio[*],source[*]', - removeformat: [{ selector: '*', attributes: ['style'], split: false, expand: false, deep: true }], - importcss_append: true, - sandbox_iframes: true, - - // Font handling - fontsize_formats: '6pt 8pt 9pt 10pt 11pt 12pt 14pt 16pt 18pt 20pt 24pt 36pt 48pt', - font_formats: - 'Arial=arial,helvetica,sans-serif;' + - 'Arial Black=arial black,avant garde;' + - 'Calibri=calibri;' + - 'Comic Sans MS=comic sans ms,sans-serif;' + - 'Courier New=courier new,courier;' + - 'Georgia=georgia,palatino;' + - 'Helvetica=helvetica;' + - 'Impact=impact,chicago;' + - 'Symbol=symbol;' + - 'Tahoma=tahoma,arial,helvetica,sans-serif;' + - 'Terminal=terminal,monaco;' + - 'Times New Roman=times new roman,times;' + - 'Verdana=verdana,geneva;' + - 'Webdings=webdings;' + - 'Wingdings=wingdings,zapf dingbats', - - // Plugins - plugins: - 'advlist autolink link image lists charmap preview anchor pagebreak ' + - 'searchreplace wordcount visualblocks visualchars code insertdatetime media nonbreaking ' + - 'save table directionality help emoticons fullscreen', - - emoticons_database: 'emojis', - - // Toolbar - menubar: false, - toolbar1: - 'newdocument | undo redo | bold italic underline subscript superscript ' + - 'strikethrough | styleselect | blocks | fontfamily | fontsize |' + - 'outdent indent | alignleft aligncenter alignright alignjustify | removeformat |' + - 'insertfile | cut copy codesample | bullist numlist |' + - 'link unlink anchor image media | charmap | insertdatetime | table |' + - 'forecolor backcolor emoticons | searchreplace | ' + - 'pagebreak | code | fullscreen | preview', - - // Formatting - style_formats: [ - { - title: 'Headers', - items: [ - { title: 'h1', block: 'h1' }, - { title: 'h2', block: 'h2' }, - { title: 'h3', block: 'h3' }, - { title: 'h4', block: 'h4' }, - { title: 'h5', block: 'h5' }, - { title: 'h6', block: 'h6' }, - ], - }, - - { - title: 'Blocks', - items: [ - { title: 'p', block: 'p' }, - { title: 'div', block: 'div' }, - { title: 'pre', block: 'pre' }, - { title: 'code', block: 'code' }, - ], - }, + const editor = document.getElementById('editor'); + if (!editor) { + return; + } - { - title: 'Containers', - items: [ - { title: 'blockquote', block: 'blockquote', wrapper: true }, - { title: 'figure', block: 'figure', wrapper: true }, - ], - }, + const joditEditor = Jodit.make(editor, { + zIndex: 0, + readonly: false, + activeButtonsInReadOnly: ['source', 'fullsize', 'print', 'about', 'dots'], + toolbarButtonSize: 'middle', + theme: 'default', + saveModeInCookie: false, + spellcheck: true, + editorCssClass: false, + triggerChangeEvent: true, + width: 'auto', + height: 'auto', + minHeight: 100, + direction: '', + language: 'auto', + debugLanguage: false, + i18n: 'en', + tabIndex: -1, + toolbar: true, + enter: 'P', + defaultMode: Jodit.MODE_WYSIWYG, + useSplitMode: false, + colors: { + greyscale: [ + '#000000', + '#434343', + '#666666', + '#999999', + '#B7B7B7', + '#CCCCCC', + '#D9D9D9', + '#EFEFEF', + '#F3F3F3', + '#FFFFFF', ], - - // File browser - file_picker_types: 'image media', - file_picker_callback: (callback, value, meta) => { - const type = meta.filetype; - const w = window, - d = document, - e = d.documentElement, - g = d.getElementsByTagName('body')[0], - x = w.innerWidth || e.clientWidth || g.clientWidth, - y = w.innerHeight || e.clientHeight || g.clientHeight; - - let mediaBrowser = './media-browser'; - mediaBrowser += mediaBrowser.indexOf('?') < 0 ? '?type=' + type : '&type=' + type; - - tinymce.activeEditor.windowManager.openUrl({ - url: mediaBrowser, - title: 'Select media file', - width: x * 0.8, - height: y * 0.8, - resizable: 'yes', - close_previous: 'no', - onMessage: function (api, data) { - if (data.mceAction === 'phpMyFAQMediaBrowserAction') { - callback(data.url); - api.close(); - } - }, - }); - }, - - // override default upload handler to simulate successful upload - images_upload_handler: (blobInfo) => - new Promise((resolve, reject) => { - const csrf = document.getElementById('pmf-csrf-token').value; - const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); - - fetch(`api/content/images?csrf=${csrf}`, { - method: 'POST', - body: formData, - //credentials: 'omit' - }) - .then((response) => { - if (response.ok) { - return response.json(); - } - throw new Error('Network response was not ok: ', { cause: { response } }); - }) - .then((json) => { - if (!json || typeof json.location != 'string') { - console.log(JSON.stringify(json)); - throw new Error('Invalid JSON: ' + JSON.stringify(json)); - } - resolve(json.location); - }) - .catch(async (error) => { - const errors = await error.cause.response.json(); - console.log(errors); - }); - }), - - // Custom params - csrf: document.getElementById('pmf-csrf-token').value, - - // Image handling - image_advtab: true, - image_class_list: [ - { title: 'None', value: '' }, - { title: 'Responsive', value: 'img-fluid' }, + palette: [ + '#980000', + '#FF0000', + '#FF9900', + '#FFFF00', + '#00F0F0', + '#00FFFF', + '#4A86E8', + '#0000FF', + '#9900FF', + '#FF00FF', + ], + full: [ + '#E6B8AF', + '#F4CCCC', + '#FCE5CD', + '#FFF2CC', + '#D9EAD3', + '#D0E0E3', + '#C9DAF8', + '#CFE2F3', + '#D9D2E9', + '#EAD1DC', + '#DD7E6B', + '#EA9999', + '#F9CB9C', + '#FFE599', + '#B6D7A8', + '#A2C4C9', + '#A4C2F4', + '#9FC5E8', + '#B4A7D6', + '#D5A6BD', + '#CC4125', + '#E06666', + '#F6B26B', + '#FFD966', + '#93C47D', + '#76A5AF', + '#6D9EEB', + '#6FA8DC', + '#8E7CC3', + '#C27BA0', + '#A61C00', + '#CC0000', + '#E69138', + '#F1C232', + '#6AA84F', + '#45818E', + '#3C78D8', + '#3D85C6', + '#674EA7', + '#A64D79', + '#85200C', + '#990000', + '#B45F06', + '#BF9000', + '#38761D', + '#134F5C', + '#1155CC', + '#0B5394', + '#351C75', + '#733554', + '#5B0F00', + '#660000', + '#783F04', + '#7F6000', + '#274E13', + '#0C343D', + '#1C4587', + '#073763', + '#20124D', + '#4C1130', ], - image_dimensions: true, - images_upload_url: '/admin/api/content/images', - automatic_uploads: true, - setup: (editor) => { - editor.on('change', () => { - tinymce.triggerSave(); - }); + }, + colorPickerDefaultTab: 'background', + imageDefaultWidth: 300, + removeButtons: [], + disablePlugins: [], + extraButtons: [], + buttons: [ + 'source', + '|', + 'bold', + 'strikethrough', + 'underline', + 'italic', + '|', + 'ul', + 'ol', + 'superscript', + 'subscript', + '|', + 'justify', + 'outdent', + 'indent', + '|', + 'font', + 'fontsize', + 'lineHeight', + 'brush', + 'paragraph', + '|', + 'copy', + 'cut', + 'paste', + 'selectall', + '|', + 'file', + 'image', + 'video', + 'table', + 'link', + '|', + 'undo', + 'redo', + '|', + 'classSpan', + 'hr', + 'eraser', + 'copyformat', + '|', + 'symbols', + 'fullsize', + 'preview', + 'print', + ], + events: {}, + textIcons: false, + uploader: { + url: '/admin/api/content/images?csrf=' + document.getElementById('pmf-csrf-token').value, + format: 'json', + isSuccess: (response) => { + return !response.error; }, - }); - } + getMessage: (response) => { + return response.msg; + }, + }, + filebrowser: { + ajax: { + url: '/admin/api/media-browser', + contentType: 'application/json; charset=UTF-8', + }, + createNewFolder: false, + deleteFolder: false, + moveFolder: false, + showFoldersPanel: false, + showFileSize: true, + showFileName: true, + }, + }); }; diff --git a/phpmyfaq/admin/assets/src/content/news.js b/phpmyfaq/admin/assets/src/content/news.js index c3c71e816e..948ce99606 100644 --- a/phpmyfaq/admin/assets/src/content/news.js +++ b/phpmyfaq/admin/assets/src/content/news.js @@ -16,8 +16,6 @@ import { activateNews, addNews, deleteNews, updateNews } from '../api'; import { Modal } from 'bootstrap'; -import { TinyMCE } from 'tinymce'; -import tinymce from 'tinymce/tinymce'; export const handleAddNews = () => { const submit = document.getElementById('submitAddNews'); @@ -32,7 +30,7 @@ export const handleAddNews = () => { }); const data = { - news: tinymce.get('editor').getContent(), + news: document.getElementById('editor').value, newsHeader: document.getElementById('newsheader').value, authorName: document.getElementById('authorName').value, authorEmail: document.getElementById('authorEmail').value, @@ -89,7 +87,7 @@ export const handleEditNews = () => { const data = { id: document.getElementById('id').value, csrfToken: document.getElementById('pmf-csrf-token').value, - news: tinymce.get('editor').getContent(), + news: document.getElementById('editor').value, newsHeader: document.getElementById('newsheader').value, authorName: document.getElementById('authorName').value, authorEmail: document.getElementById('authorEmail').value, diff --git a/phpmyfaq/assets/templates/admin/content/media.browser.twig b/phpmyfaq/assets/templates/admin/content/media.browser.twig deleted file mode 100644 index 7368bae320..0000000000 --- a/phpmyfaq/assets/templates/admin/content/media.browser.twig +++ /dev/null @@ -1,39 +0,0 @@ - - - - - phpMyFAQ Media Browser - - - - -
-
- - -
-
- -{% if isImageDirectoryMissing %} - -{% endif %} - -{% for image in images %} -
- {{ image }}{{ image }} -
-{% endfor %} - - - - - diff --git a/phpmyfaq/src/admin-api-routes.php b/phpmyfaq/src/admin-api-routes.php index aad1130a86..f636ed6631 100644 --- a/phpmyfaq/src/admin-api-routes.php +++ b/phpmyfaq/src/admin-api-routes.php @@ -30,6 +30,7 @@ use phpMyFAQ\Controller\Administration\Api\ImageController; use phpMyFAQ\Controller\Administration\Api\InstanceController; use phpMyFAQ\Controller\Administration\Api\MarkdownController; +use phpMyFAQ\Controller\Administration\Api\MediaBrowserController; use phpMyFAQ\Controller\Administration\Api\NewsController; use phpMyFAQ\Controller\Administration\Api\QuestionController; use phpMyFAQ\Controller\Administration\Api\SearchController; @@ -193,6 +194,12 @@ 'controller' => [MarkdownController::class, 'renderMarkdown'], 'methods' => 'POST' ], + + 'admin.api.media.browser' => [ + 'path' => '/media-browser', + 'controller' => [MediaBrowserController::class, 'index'], + 'methods' => 'GET' + ], // Dashboard API 'admin.api.dashboard.topten' => [ 'path' => '/dashboard/topten', diff --git a/phpmyfaq/src/admin-routes.php b/phpmyfaq/src/admin-routes.php index 9075ed8149..781ccb58a7 100644 --- a/phpmyfaq/src/admin-routes.php +++ b/phpmyfaq/src/admin-routes.php @@ -31,7 +31,6 @@ use phpMyFAQ\Controller\Administration\GroupController; use phpMyFAQ\Controller\Administration\ImportController; use phpMyFAQ\Controller\Administration\InstanceController; -use phpMyFAQ\Controller\Administration\MediaBrowserController; use phpMyFAQ\Controller\Administration\NewsController; use phpMyFAQ\Controller\Administration\OpenQuestionsController; use phpMyFAQ\Controller\Administration\PasswordChangeController; @@ -273,11 +272,6 @@ 'controller' => [InstanceController::class, 'index'], 'methods' => 'GET' ], - 'admin.media.browser' => [ - 'path' => '/media-browser', - 'controller' => [MediaBrowserController::class, 'index'], - 'methods' => 'GET' - ], 'admin.news' => [ 'path' => '/news', 'controller' => [NewsController::class, 'index'], diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ImageController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ImageController.php index a97eeeb265..fcb65f2ae7 100644 --- a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ImageController.php +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/ImageController.php @@ -17,6 +17,7 @@ namespace phpMyFAQ\Controller\Administration\Api; +use DateTime; use phpMyFAQ\Controller\AbstractController; use phpMyFAQ\Core\Exception; use phpMyFAQ\Enums\PermissionType; @@ -30,56 +31,96 @@ class ImageController extends AbstractController { /** - * @throws Exception + * @throws Exception|\Exception */ #[Route('admin/api/content/images')] public function upload(Request $request): JsonResponse { $this->userHasPermission(PermissionType::FAQ_EDIT); + $session = $this->container->get('session'); + $uploadDir = PMF_CONTENT_DIR . '/user/images/'; $validFileExtensions = ['gif', 'jpg', 'jpeg', 'png']; $timestamp = time(); - if ( - !Token::getInstance($this->container->get('session')) - ->verifyToken('edit-faq', $request->query->get('csrf')) - ) { - return $this->json(['error' => Translation::get('msgNoPermission')], Response::HTTP_UNAUTHORIZED); + if (!Token::getInstance($session)->verifyToken('edit-faq', $request->query->get('csrf'))) { + return $this->json( + [ + 'success' => false, + 'data' => ['code' => Response::HTTP_UNAUTHORIZED], + 'messages' => [Translation::get('msgNoPermission')] + ], + Response::HTTP_UNAUTHORIZED + ); } - $file = $request->files->get('file'); - $headers = []; - if ($file && $file->isValid()) { - if ( - $request->server->get('HTTP_ORIGIN') !== null && - $request->server->get('HTTP_ORIGIN') . '/' === $this->configuration->getDefaultUrl() - ) { - $headers = ['Access-Control-Allow-Origin', $request->server->get('HTTP_ORIGIN')]; - } + $files = $request->files->get('files'); - // Sanitize input - if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", $file->getClientOriginalName())) { - return $this->json([], Response::HTTP_BAD_REQUEST, $headers); - } + $uploadedFiles = []; + foreach ($files as $file) { + $headers = []; + if ($file && $file->isValid()) { + if ( + $request->server->get('HTTP_ORIGIN') !== null && + $request->server->get('HTTP_ORIGIN') . '/' === $this->configuration->getDefaultUrl() + ) { + $headers = ['Access-Control-Allow-Origin', $request->server->get('HTTP_ORIGIN')]; + } - // Verify extension - if (!in_array(strtolower($file->getClientOriginalExtension()), $validFileExtensions)) { - return $this->json([], Response::HTTP_BAD_REQUEST, $headers); - } + // Sanitize input + if (preg_match("/([^\w\s\d\-_~,;:\[\]\(\).])|([\.]{2,})/", $file->getClientOriginalName())) { + return $this->json( + [ + 'success' => false, + 'data' => ['code' => Response::HTTP_BAD_REQUEST], + 'messages' => ['Data contains invalid characters'] + ], + Response::HTTP_BAD_REQUEST, + $headers + ); + } - // Accept upload if there was no origin, or if it is an accepted origin - $fileName = $timestamp . '_' . $file->getClientOriginalName(); - $file->move($uploadDir, $fileName); + // Verify extension + if (!in_array(strtolower($file->getClientOriginalExtension()), $validFileExtensions)) { + return $this->json( + [ + 'success' => false, + 'data' => ['code' => Response::HTTP_BAD_REQUEST], + 'messages' => ['File extension not allowed'] + ], + Response::HTTP_BAD_REQUEST, + $headers + ); + } - // Respond to the successful upload with JSON with the full URL of the uploaded image. - return $this->json( - ['location' => $this->configuration->getDefaultUrl() . 'content/user/images/' . $fileName], - Response::HTTP_OK, - $headers - ); - } else { - return $this->json([], Response::HTTP_BAD_REQUEST, $headers); + // Accept upload if there was no origin, or if it is an accepted origin + $fileName = $timestamp . '_' . $file->getClientOriginalName(); + $file->move($uploadDir, $fileName); + + // Add to the list of uploaded files + $uploadedFiles[] = $fileName; + } else { + return $this->json(['success' => false], Response::HTTP_BAD_REQUEST, $headers); + } } + + $response = [ + 'success' => true, + 'time' => (new DateTime())->format('Y-m-d H:i:s'), + 'data' => [ + 'sources' => [ + [ + 'baseurl' => $this->configuration->getDefaultUrl(), + 'path' => 'content/user/images/', + 'files' => $uploadedFiles, + 'name' => 'default' + ] + ], + 'code' => 220 + ] + ]; + + return $this->json($response, Response::HTTP_OK); } } diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php new file mode 100644 index 0000000000..bbec51d23e --- /dev/null +++ b/phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/MediaBrowserController.php @@ -0,0 +1,114 @@ + + * @copyright 2024 phpMyFAQ Team + * @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 + * @link https://www.phpmyfaq.de + * @since 2024-12-28 + */ + +declare(strict_types=1); + +namespace phpMyFAQ\Controller\Administration\Api; + +use DateTime; +use phpMyFAQ\Controller\AbstractController; +use phpMyFAQ\Core\Exception; +use phpMyFAQ\Enums\PermissionType; +use phpMyFAQ\Filter; +use phpMyFAQ\Translation; +use phpMyFAQ\Utils; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Attribute\Route; +use Twig\Error\LoaderError; + +class MediaBrowserController extends AbstractController +{ + /** + * @throws LoaderError + * @throws Exception + */ + #[Route('admin/api/media-browser', name: 'admin.media.browser', methods: ['GET'])] + public function index(Request $request): JsonResponse|Response + { + $this->userHasPermission(PermissionType::FAQ_EDIT); + + $allowedExtensions = ['png', 'gif', 'jpg', 'jpeg', 'mov', 'mpg', 'mp4', 'ogg', 'wmv', 'avi', 'webm']; + + if (!is_dir(PMF_CONTENT_DIR . '/user/images')) { + return $this->json( + ['error' => sprintf(Translation::get('ad_dir_missing'), '/images')], + Response::HTTP_BAD_REQUEST + ); + } + + $data = json_decode($request->getContent()); + $action = Filter::filterVar($data->action, FILTER_SANITIZE_SPECIAL_CHARS); + + if ($action === 'fileRemove') { + $file = Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS); + $file = PMF_CONTENT_DIR . '/user/images/' . $file; + + if (file_exists($file)) { + unlink($file); + } + + $response = [ + 'success' => true, + 'data' => [ + 'code' => 220, + ] + ]; + + return $this->json($response, Response::HTTP_OK); + } + + $files = []; + if (is_dir(PMF_CONTENT_DIR . '/user/images')) { + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(PMF_CONTENT_DIR . '/user/images')); + foreach ($iterator as $file) { + if ($file->isDir() || !in_array(strtolower($file->getExtension()), $allowedExtensions)) { + continue; + } + + $files[] = [ + 'file' => $file->getFilename(), + 'size' => Utils::formatBytes($file->getSize()), + 'isImage' => true, + 'thumb' => $file->getFilename(), + 'changed' => date('Y-m-d H:i:s', $file->getMTime()), + ]; + } + } + + $response = [ + 'success' => true, + 'time' => (new DateTime())->format('Y-m-d H:i:s'), + 'data' => [ + 'sources' => [ + [ + 'baseurl' => $this->configuration->getDefaultUrl(), + 'path' => 'content/user/images/', + 'files' => $files, + 'name' => 'default' + ] + ], + 'code' => 220 + ] + ]; + + return $this->json($response, Response::HTTP_OK); + } +} diff --git a/phpmyfaq/src/phpMyFAQ/Controller/Administration/MediaBrowserController.php b/phpmyfaq/src/phpMyFAQ/Controller/Administration/MediaBrowserController.php deleted file mode 100644 index 99c5020fc4..0000000000 --- a/phpmyfaq/src/phpMyFAQ/Controller/Administration/MediaBrowserController.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @copyright 2024 phpMyFAQ Team - * @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 - * @link https://www.phpmyfaq.de - * @since 2024-12-28 - */ - -declare(strict_types=1); - -namespace phpMyFAQ\Controller\Administration; - -use phpMyFAQ\Core\Exception; -use phpMyFAQ\Enums\PermissionType; -use phpMyFAQ\Translation; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Attribute\Route; -use Twig\Error\LoaderError; - -class MediaBrowserController extends AbstractAdministrationController -{ - /** - * @throws LoaderError - * @throws Exception - */ - #[Route('/media-browser', name: 'admin.media.browser', methods: ['GET'])] - public function index(): Response - { - $this->userHasPermission(PermissionType::FAQ_EDIT); - - $allowedExtensions = ['png', 'gif', 'jpg', 'jpeg', 'mov', 'mpg', 'mp4', 'ogg', 'wmv', 'avi', 'webm']; - - $images = []; - if (is_dir(PMF_CONTENT_DIR . '/user/images')) { - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(PMF_CONTENT_DIR . '/user/images')); - foreach ($files as $file) { - if ($file->isDir() || !in_array(strtolower($file->getExtension()), $allowedExtensions)) { - continue; - } - - $path = str_replace(dirname(__DIR__, 4) . '/', '', (string)$file->getPath()); - $images[] = $this->configuration->getDefaultUrl() . $path . '/' . $file->getFilename(); - } - } - - return $this->render( - '@admin/content/media.browser.twig', - [ - 'msgNotAuthenticated' => Translation::get('msgNoPermission'), - 'msgMediaSearch' => Translation::get('ad_media_name_search'), - 'isImageDirectoryMissing' => !is_dir(PMF_CONTENT_DIR . '/user/images'), - 'msgImageDirectoryMissing' => sprintf(Translation::get('ad_dir_missing'), '/images'), - 'images' => $images, - ] - ); - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 517d544ef4..6ecfe58e50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,17 +27,17 @@ dependencies: specifier: 4.7.8 version: 4.7.8 highlight.js: - specifier: ^11.11.0 - version: 11.11.0 + specifier: ^11.11.1 + version: 11.11.1 + jodit: + specifier: ^4.2.47 + version: 4.2.47 masonry-layout: specifier: ^4.2.2 version: 4.2.2 sortablejs: specifier: ^1.15.6 version: 1.15.6 - tinymce: - specifier: ^6.8.5 - version: 6.8.5 vanilla-cookieconsent: specifier: ^3.0.1 version: 3.0.1 @@ -92,20 +92,20 @@ devDependencies: specifier: ^1.0.1 version: 1.0.1 vite: - specifier: ^6.0.5 - version: 6.0.5(@types/node@20.17.10)(sass@1.83.0) + specifier: ^6.0.6 + version: 6.0.6(@types/node@20.17.10)(sass@1.83.0) vite-plugin-compression: specifier: ^0.5.1 - version: 0.5.1(vite@6.0.5) + version: 0.5.1(vite@6.0.6) vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@6.0.5) + version: 3.2.2(vite@6.0.6) vite-plugin-minify: specifier: ^2.1.0 - version: 2.1.0(vite@6.0.5) + version: 2.1.0(vite@6.0.6) vite-plugin-static-copy: specifier: ^2.2.0 - version: 2.2.0(vite@6.0.5) + version: 2.2.0(vite@6.0.6) vitest: specifier: ^2.1.8 version: 2.1.8(@types/node@20.17.10)(jsdom@25.0.1)(sass@1.83.0) @@ -1173,7 +1173,7 @@ packages: '@commitlint/load': 19.6.1(@types/node@20.17.10)(typescript@5.7.2) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 - tinyexec: 0.3.1 + tinyexec: 0.3.2 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' @@ -1280,7 +1280,7 @@ packages: '@commitlint/types': 19.5.0 git-raw-commits: 4.0.0 minimist: 1.2.8 - tinyexec: 0.3.1 + tinyexec: 0.3.2 dev: true /@commitlint/resolve-extends@19.5.0: @@ -1351,8 +1351,8 @@ packages: dev: true optional: true - /@esbuild/aix-ppc64@0.24.0: - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + /@esbuild/aix-ppc64@0.24.2: + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -1369,8 +1369,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.24.0: - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + /@esbuild/android-arm64@0.24.2: + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1387,8 +1387,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.24.0: - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + /@esbuild/android-arm@0.24.2: + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1405,8 +1405,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.24.0: - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + /@esbuild/android-x64@0.24.2: + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1423,8 +1423,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.24.0: - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + /@esbuild/darwin-arm64@0.24.2: + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1441,8 +1441,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.24.0: - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + /@esbuild/darwin-x64@0.24.2: + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1459,8 +1459,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.24.0: - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + /@esbuild/freebsd-arm64@0.24.2: + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1477,8 +1477,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.24.0: - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + /@esbuild/freebsd-x64@0.24.2: + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1495,8 +1495,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.24.0: - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + /@esbuild/linux-arm64@0.24.2: + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1513,8 +1513,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.24.0: - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + /@esbuild/linux-arm@0.24.2: + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1531,8 +1531,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.24.0: - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + /@esbuild/linux-ia32@0.24.2: + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1549,8 +1549,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.24.0: - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + /@esbuild/linux-loong64@0.24.2: + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1567,8 +1567,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.24.0: - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + /@esbuild/linux-mips64el@0.24.2: + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1585,8 +1585,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.24.0: - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + /@esbuild/linux-ppc64@0.24.2: + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1603,8 +1603,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.24.0: - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + /@esbuild/linux-riscv64@0.24.2: + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1621,8 +1621,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.24.0: - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + /@esbuild/linux-s390x@0.24.2: + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1639,8 +1639,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.24.0: - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + /@esbuild/linux-x64@0.24.2: + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1648,6 +1648,15 @@ packages: dev: true optional: true + /@esbuild/netbsd-arm64@0.24.2: + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + /@esbuild/netbsd-x64@0.21.5: resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -1657,8 +1666,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.24.0: - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + /@esbuild/netbsd-x64@0.24.2: + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1666,8 +1675,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-arm64@0.24.0: - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + /@esbuild/openbsd-arm64@0.24.2: + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1684,8 +1693,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.24.0: - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + /@esbuild/openbsd-x64@0.24.2: + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1702,8 +1711,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.24.0: - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + /@esbuild/sunos-x64@0.24.2: + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1720,8 +1729,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.24.0: - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + /@esbuild/win32-arm64@0.24.2: + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1738,8 +1747,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.24.0: - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + /@esbuild/win32-ia32@0.24.2: + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1756,8 +1765,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.24.0: - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + /@esbuild/win32-x64@0.24.2: + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -2480,6 +2489,11 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true + /autobind-decorator@2.4.0: + resolution: {integrity: sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==} + engines: {node: '>=8.10', npm: '>=6.4.1'} + dev: false + /autocompleter@9.3.2: resolution: {integrity: sha512-rLbf2TLGOD7y+gOS36ksrZdIsvoHa2KXc2A7503w+NBRPrcF73zzFeYBxEcV/iMPjaBH3jFhNIYObZ7zt1fkCQ==} dev: false @@ -3047,7 +3061,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.75 + electron-to-chromium: 1.5.76 dev: true /browserslist@4.24.3: @@ -3056,7 +3070,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001690 - electron-to-chromium: 1.5.75 + electron-to-chromium: 1.5.76 node-releases: 2.0.19 update-browserslist-db: 1.1.1(browserslist@4.24.3) dev: true @@ -3493,8 +3507,8 @@ packages: jake: 10.9.2 dev: true - /electron-to-chromium@1.5.75: - resolution: {integrity: sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==} + /electron-to-chromium@1.5.76: + resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} dev: true /emoji-regex@8.0.0: @@ -3525,8 +3539,8 @@ packages: is-arrayish: 0.2.1 dev: true - /es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + /es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} dev: true /esbuild@0.21.5: @@ -3560,36 +3574,37 @@ packages: '@esbuild/win32-x64': 0.21.5 dev: true - /esbuild@0.24.0: - resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + /esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} engines: {node: '>=18'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.24.0 - '@esbuild/android-arm': 0.24.0 - '@esbuild/android-arm64': 0.24.0 - '@esbuild/android-x64': 0.24.0 - '@esbuild/darwin-arm64': 0.24.0 - '@esbuild/darwin-x64': 0.24.0 - '@esbuild/freebsd-arm64': 0.24.0 - '@esbuild/freebsd-x64': 0.24.0 - '@esbuild/linux-arm': 0.24.0 - '@esbuild/linux-arm64': 0.24.0 - '@esbuild/linux-ia32': 0.24.0 - '@esbuild/linux-loong64': 0.24.0 - '@esbuild/linux-mips64el': 0.24.0 - '@esbuild/linux-ppc64': 0.24.0 - '@esbuild/linux-riscv64': 0.24.0 - '@esbuild/linux-s390x': 0.24.0 - '@esbuild/linux-x64': 0.24.0 - '@esbuild/netbsd-x64': 0.24.0 - '@esbuild/openbsd-arm64': 0.24.0 - '@esbuild/openbsd-x64': 0.24.0 - '@esbuild/sunos-x64': 0.24.0 - '@esbuild/win32-arm64': 0.24.0 - '@esbuild/win32-ia32': 0.24.0 - '@esbuild/win32-x64': 0.24.0 + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 dev: true /escalade@3.2.0: @@ -3934,8 +3949,8 @@ packages: hasBin: true dev: true - /highlight.js@11.11.0: - resolution: {integrity: sha512-6ErL7JlGu2CNFHyRQEuDogOyGPNiqcuWdt4iSSFUPyferNTGlNTPFqeV36Y/XwA4V/TJ8l0sxp6FTnxud/mf8g==} + /highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} dev: false @@ -4104,6 +4119,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + requiresBuild: true dev: true /is-fullwidth-code-point@3.0.0: @@ -4205,6 +4221,12 @@ packages: hasBin: true dev: true + /jodit@4.2.47: + resolution: {integrity: sha512-3dSdV+dUjwbuNKhn2M6hs8F7qkiufBPJFMB6YVJWbja7RqkQHGtrCGkpZJ1PH16chUgET2Yi+LVOonWyf/nV8g==} + dependencies: + autobind-decorator: 2.4.0 + dev: false + /jquery@3.7.1: resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==} dev: false @@ -5424,14 +5446,10 @@ packages: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true - /tinyexec@0.3.1: - resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + /tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} dev: true - /tinymce@6.8.5: - resolution: {integrity: sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==} - dev: false - /tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -5447,15 +5465,15 @@ packages: engines: {node: '>=14.0.0'} dev: true - /tldts-core@6.1.69: - resolution: {integrity: sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==} + /tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} dev: true - /tldts@6.1.69: - resolution: {integrity: sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==} + /tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} hasBin: true dependencies: - tldts-core: 6.1.69 + tldts-core: 6.1.70 dev: true /to-fast-properties@1.0.3: @@ -5474,7 +5492,7 @@ packages: resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} engines: {node: '>=16'} dependencies: - tldts: 6.1.69 + tldts: 6.1.70 dev: true /tr46@0.0.3: @@ -5588,7 +5606,7 @@ packages: dependencies: cac: 6.7.14 debug: 4.4.0 - es-module-lexer: 1.5.4 + es-module-lexer: 1.6.0 pathe: 1.1.2 vite: 5.4.11(@types/node@20.17.10)(sass@1.83.0) transitivePeerDependencies: @@ -5603,7 +5621,7 @@ packages: - terser dev: true - /vite-plugin-compression@0.5.1(vite@6.0.5): + /vite-plugin-compression@0.5.1(vite@6.0.6): resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} peerDependencies: vite: '>=2.0.0' @@ -5611,12 +5629,12 @@ packages: chalk: 4.1.2 debug: 4.4.0 fs-extra: 10.1.0 - vite: 6.0.5(@types/node@20.17.10)(sass@1.83.0) + vite: 6.0.6(@types/node@20.17.10)(sass@1.83.0) transitivePeerDependencies: - supports-color dev: true - /vite-plugin-html@3.2.2(vite@6.0.5): + /vite-plugin-html@3.2.2(vite@6.0.6): resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==} peerDependencies: vite: '>=2.0.0' @@ -5633,20 +5651,20 @@ packages: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 6.0.5(@types/node@20.17.10)(sass@1.83.0) + vite: 6.0.6(@types/node@20.17.10)(sass@1.83.0) dev: true - /vite-plugin-minify@2.1.0(vite@6.0.5): + /vite-plugin-minify@2.1.0(vite@6.0.6): resolution: {integrity: sha512-h+Ae0WX0mvrWM4GhE6JX2NmxlDuZRv4AgcTHFqfbSyPwS+OdlOM692AQrK0eO8lDEh7gYLjG3EHovKvtrNQDvA==} peerDependencies: vite: '>=5' dependencies: '@types/html-minifier-terser': 7.0.2 html-minifier-terser: 7.2.0 - vite: 6.0.5(@types/node@20.17.10)(sass@1.83.0) + vite: 6.0.6(@types/node@20.17.10)(sass@1.83.0) dev: true - /vite-plugin-static-copy@2.2.0(vite@6.0.5): + /vite-plugin-static-copy@2.2.0(vite@6.0.6): resolution: {integrity: sha512-ytMrKdR9iWEYHbUxs6x53m+MRl4SJsOSoMu1U1+Pfg0DjPeMlsRVx3RR5jvoonineDquIue83Oq69JvNsFSU5w==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: @@ -5656,7 +5674,7 @@ packages: fast-glob: 3.3.2 fs-extra: 11.2.0 picocolors: 1.1.1 - vite: 6.0.5(@types/node@20.17.10)(sass@1.83.0) + vite: 6.0.6(@types/node@20.17.10)(sass@1.83.0) dev: true /vite@5.4.11(@types/node@20.17.10)(sass@1.83.0): @@ -5699,8 +5717,8 @@ packages: fsevents: 2.3.3 dev: true - /vite@6.0.5(@types/node@20.17.10)(sass@1.83.0): - resolution: {integrity: sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==} + /vite@6.0.6(@types/node@20.17.10)(sass@1.83.0): + resolution: {integrity: sha512-NSjmUuckPmDU18bHz7QZ+bTYhRR0iA72cs2QAxCqDpafJ0S6qetco0LB3WW2OxlMHS0JmAv+yZ/R3uPmMyGTjQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -5740,7 +5758,7 @@ packages: optional: true dependencies: '@types/node': 20.17.10 - esbuild: 0.24.0 + esbuild: 0.24.2 postcss: 8.4.49 rollup: 4.29.1 sass: 1.83.0 @@ -5798,7 +5816,7 @@ packages: pathe: 1.1.2 std-env: 3.8.0 tinybench: 2.9.0 - tinyexec: 0.3.1 + tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 1.2.0 vite: 5.4.11(@types/node@20.17.10)(sass@1.83.0) diff --git a/vite.config.js b/vite.config.js index 741d6cfdc7..baa3fbff91 100644 --- a/vite.config.js +++ b/vite.config.js @@ -28,7 +28,6 @@ export default defineConfig({ manualChunks: { bootstrap: ['bootstrap'], chart: ['chart.js'], - tinymce: ['tinymce'], }, }, }, @@ -40,11 +39,6 @@ export default defineConfig({ viteCompression(), viteStaticCopy({ targets: [ - { - src: path.resolve(__dirname, 'phpmyfaq/admin/assets/src/tinymce/phpmyfaq.tinymce.plugin.js'), - dest: '../phpmyfaq/assets/public/plugins/phpmyfaq', - rename: 'plugin.js', - }, { src: path.resolve(__dirname, 'phpmyfaq/assets/fonts/*'), dest: '../phpmyfaq/assets/public/fonts',