diff --git a/formwork/src/Panel/Controllers/PagesController.php b/formwork/src/Panel/Controllers/PagesController.php index d0b875f3e..e16b01bd1 100644 --- a/formwork/src/Panel/Controllers/PagesController.php +++ b/formwork/src/Panel/Controllers/PagesController.php @@ -322,6 +322,9 @@ public function delete(RouteParams $routeParams): Response $page = $this->site->findPage($routeParams->get('page')); if ($page === null) { + if ($this->request->isXmlHttpRequest()) { + return JsonResponse::error($this->translate('panel.pages.page.cannotDelete.pageNotFound'), ResponseStatus::InternalServerError); + } $this->panel->notify($this->translate('panel.pages.page.cannotDelete.pageNotFound'), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.pages'), base: $this->panel->panelRoot()); } @@ -331,12 +334,18 @@ public function delete(RouteParams $routeParams): Response if ($page->languages()->available()->has($language)) { $page->setLanguage($language); } else { + if ($this->request->isXmlHttpRequest()) { + return JsonResponse::error($this->translate('panel.pages.page.cannotDelete.invalidLanguage', $language), ResponseStatus::InternalServerError); + } $this->panel->notify($this->translate('panel.pages.page.cannotDelete.invalidLanguage', $language), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.pages'), base: $this->panel->panelRoot()); } } if (!$page->isDeletable()) { + if ($this->request->isXmlHttpRequest()) { + return JsonResponse::error($this->translate('panel.pages.page.cannotDelete.notDeletable'), ResponseStatus::InternalServerError); + } $this->panel->notify($this->translate('panel.pages.page.cannotDelete.notDeletable'), 'error'); return $this->redirectToReferer(default: $this->generateRoute('panel.pages'), base: $this->panel->panelRoot()); } @@ -350,6 +359,9 @@ public function delete(RouteParams $routeParams): Response } } + if ($this->request->isXmlHttpRequest()) { + return JsonResponse::success($this->translate('panel.pages.page.deleted')); + } $this->panel->notify($this->translate('panel.pages.page.deleted'), 'success'); // Try to redirect to referer unless it's to Pages@edit diff --git a/panel/config/routes/routes.php b/panel/config/routes/routes.php index b34106b1b..4a1bdc00c 100644 --- a/panel/config/routes/routes.php +++ b/panel/config/routes/routes.php @@ -95,6 +95,7 @@ 'path' => '/pages/{page:all}/delete/', 'action' => 'Formwork\Panel\Controllers\PagesController@delete', 'methods' => ['POST'], + 'types' => ['HTTP', 'XHR'], ], 'panel.pages.delete.lang' => [ diff --git a/panel/modals/deletePageItem.yaml b/panel/modals/deletePageItem.yaml new file mode 100644 index 000000000..daf876ea0 --- /dev/null +++ b/panel/modals/deletePageItem.yaml @@ -0,0 +1,18 @@ +title: '{{panel.pages.deletePage}}' + +message: '{{panel.pages.deletePage.prompt}}' + +buttons: + dismiss: + action: dismiss + icon: times-circle + label: '{{panel.modal.action.cancel}}' + variant: secondary + + delete: + action: command + icon: trash + label: '{{panel.modal.action.delete}}' + align: right + variant: danger + command: delete-page diff --git a/panel/src/ts/components/views/pages.ts b/panel/src/ts/components/views/pages.ts index 59eff67e4..bfc93abca 100644 --- a/panel/src/ts/components/views/pages.ts +++ b/panel/src/ts/components/views/pages.ts @@ -18,6 +18,7 @@ export class Pages { const searchInput = $(".page-search"); const newPageModal = app.modals["newPageModal"]; + const deletePageItemModal = app.modals["deletePageItemModal"]; $$(".pages-tree").forEach((element) => { if (element.dataset.orderableChildren === "true") { @@ -160,6 +161,52 @@ export class Pages { filterAllowedTemplates(); } + if (deletePageItemModal) { + deletePageItemModal.onOpen((modal, trigger) => { + if (trigger) { + Object.assign(modal.data, { + action: trigger.dataset.action, + pageItem: trigger.closest(".pages-tree-item"), + }); + } + }); + + deletePageItemModal.onCommand("delete-page", (modal) => { + const { action, pageItem } = modal.data as { action: string; pageItem: HTMLElement }; + + new Request( + { + method: "POST", + url: action as string, + data: { + "csrf-token": app.config.csrfToken as string, + }, + }, + (response) => { + if (response.status === "success" && pageItem) { + const parentItem = pageItem.parentElement?.closest(".pages-tree-item"); + pageItem.remove(); + setCommandsState(); + + if (parentItem && $$(".pages-tree-item", parentItem).length === 0) { + parentItem.classList.remove("has-children", "is-expanded"); + + const deleteButton = $("[data-modal=deletePageItemModal]", parentItem) as HTMLButtonElement; + if (deleteButton) { + deleteButton.disabled = false; + } + } + } + + const notification = new Notification(response.message, response.status); + notification.show(); + + modal.close(); + }, + ); + }); + } + if (commandPreview) { const editorForm = app.forms["page-editor-form"]; diff --git a/panel/views/pages/tree.php b/panel/views/pages/tree.php index f552d603e..25a07e42f 100644 --- a/panel/views/pages/tree.php +++ b/panel/views/pages/tree.php @@ -1,4 +1,4 @@ -modals()->add('deletePage') ?> +modals()->add('deletePageItem') ?>