From 075e5534d6e217c187fb0bbf52738f3e644cc5c2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 13 Oct 2025 08:53:55 +0200 Subject: [PATCH 1/3] IBX-10609: Enhance notification deletion by integrating modal confirmation. --- .../js/scripts/admin.notifications.modal.js | 30 +++++++++++++------ .../public/js/scripts/helpers/modal.helper.js | 3 +- .../account/notifications/list_item.html.twig | 17 ++++++++++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 7b97c35064..f23c20f9a2 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -1,7 +1,10 @@ +import { controlZIndex } from './helpers/modal.helper'; + (function (global, doc, ibexa, Translator, Routing) { let currentPageLink = null; let getNotificationsStatusErrorShowed = false; let lastFailedCountFetchNotificationNode = null; + let selectedNotificationId = null; const SELECTOR_MODAL_ITEM = '.ibexa-notifications-modal__item'; const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__results .ibexa-scrollable-wrapper'; const SELECTOR_MODAL_TITLE = '.ibexa-side-panel__header'; @@ -214,9 +217,8 @@ currentTarget.textContent.trim() === markAsReadLabel ? markAsRead({ currentTarget }) : markAsUnread({ currentTarget }); }; - const deleteNotification = ({ currentTarget }) => { - const { notificationId } = currentTarget.dataset; - const deleteLink = Routing.generate('ibexa.notifications.delete', { notificationId }); + const deleteNotification = () => { + const deleteLink = Routing.generate('ibexa.notifications.delete', { notificationId: selectedNotificationId }); const message = Translator.trans( /* @Desc("Cannot delete notification") */ 'notifications.modal.message.error.delete', {}, @@ -227,11 +229,10 @@ .then(getJsonFromResponse) .then((response) => { if (response.status === 'success') { - const notification = doc.querySelector(`.ibexa-notifications-modal__item[data-notification-id="${notificationId}"]`); - const menuBranch = currentTarget.closest('.ibexa-multilevel-popup-menu__branch'); - const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + const notification = doc.querySelector( + `.ibexa-notifications-modal__item[data-notification-id="${selectedNotificationId}"]`, + ); - menuInstance.closeMenu(); notification.remove(); getNotificationsStatus(); } else { @@ -245,15 +246,26 @@ const attachActionsListeners = () => { const attachListener = (node, callback) => node.addEventListener('click', callback, false); const markAsButtons = doc.querySelectorAll('.ibexa-notifications-modal--mark-as'); - const deleteButtons = doc.querySelectorAll('.ibexa-notifications-modal--delete'); + const deleteButtons = doc.querySelectorAll('.ibexa-notifications-open-modal-button'); + const confirmDeleteButton = doc.querySelector('.ibexa-notifications-modal--delete--confirm'); + const setNotificationId = ({ currentTarget }) => { + const deleteModal = doc.querySelector('.modal-backdrop.show.fade'); + controlZIndex(deleteModal, '199'); + + selectedNotificationId = currentTarget.dataset.notificationId; + }; markAsButtons.forEach((markAsButton) => { attachListener(markAsButton, handleMarkAsAction); }); deleteButtons.forEach((deleteButton) => { - attachListener(deleteButton, deleteNotification); + attachListener(deleteButton, setNotificationId); }); + + if (confirmDeleteButton) { + confirmDeleteButton.addEventListener('click', deleteNotification); + } }; const showNotificationPage = (pageHtml) => { const modalResults = panel.querySelector(SELECTOR_MODAL_RESULTS); diff --git a/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js b/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js index 8de35aa680..46fcdc1a1a 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js @@ -1,5 +1,6 @@ -const controlZIndex = (container) => { +const controlZIndex = (container, ZIndexValue) => { const initialZIndex = container.style.zIndex; + container.style.zIndex = ZIndexValue; container.addEventListener('show.bs.modal', () => { container.style.zIndex = 'initial'; diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index cc8aac9385..6a21a30288 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -47,7 +47,11 @@ {% set popup_items = popup_items|merge([{ label: 'notification.delete'|trans|desc('Delete'), - action_attr: { class: 'ibexa-notifications-modal--delete', 'data-notification-id': notification.id }, + action_attr: { + class: 'ibexa-notifications-open-modal-button', + 'data-notification-id': notification.id, + 'data-bs-toggle': 'modal', + 'data-bs-target': '#delete-notification-modal', } }]) %} {% embed '@ibexadesign/ui/component/table/table_body_row.html.twig' with { @@ -103,3 +107,14 @@ {% endembed %} {% endblock %} {% endembed %} + +{% embed '@ibexadesign/ui/modal/delete_confirmation.html.twig' with { + id: 'delete-notification-modal', + message: 'notification.modal.delete.confirm_message'|trans|desc('Are you sure you want to delete this notification?'), +} %} + {% block confirm_button %} + + {% endblock %} +{% endembed %} From f600deb3063704d5d6ac366416edcc019f464685 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 13 Oct 2025 08:53:55 +0200 Subject: [PATCH 2/3] IBX-10609: Enhance notification deletion by integrating modal confirmation. --- .../js/scripts/admin.notifications.modal.js | 30 +++++++++++++------ .../public/js/scripts/helpers/modal.helper.js | 3 +- .../translations/ibexa_notifications.en.xliff | 5 ++++ .../account/notifications/list_item.html.twig | 17 ++++++++++- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js index 7b97c35064..f23c20f9a2 100644 --- a/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js +++ b/src/bundle/Resources/public/js/scripts/admin.notifications.modal.js @@ -1,7 +1,10 @@ +import { controlZIndex } from './helpers/modal.helper'; + (function (global, doc, ibexa, Translator, Routing) { let currentPageLink = null; let getNotificationsStatusErrorShowed = false; let lastFailedCountFetchNotificationNode = null; + let selectedNotificationId = null; const SELECTOR_MODAL_ITEM = '.ibexa-notifications-modal__item'; const SELECTOR_MODAL_RESULTS = '.ibexa-notifications-modal__results .ibexa-scrollable-wrapper'; const SELECTOR_MODAL_TITLE = '.ibexa-side-panel__header'; @@ -214,9 +217,8 @@ currentTarget.textContent.trim() === markAsReadLabel ? markAsRead({ currentTarget }) : markAsUnread({ currentTarget }); }; - const deleteNotification = ({ currentTarget }) => { - const { notificationId } = currentTarget.dataset; - const deleteLink = Routing.generate('ibexa.notifications.delete', { notificationId }); + const deleteNotification = () => { + const deleteLink = Routing.generate('ibexa.notifications.delete', { notificationId: selectedNotificationId }); const message = Translator.trans( /* @Desc("Cannot delete notification") */ 'notifications.modal.message.error.delete', {}, @@ -227,11 +229,10 @@ .then(getJsonFromResponse) .then((response) => { if (response.status === 'success') { - const notification = doc.querySelector(`.ibexa-notifications-modal__item[data-notification-id="${notificationId}"]`); - const menuBranch = currentTarget.closest('.ibexa-multilevel-popup-menu__branch'); - const menuInstance = ibexa.helpers.objectInstances.getInstance(menuBranch.menuInstanceElement); + const notification = doc.querySelector( + `.ibexa-notifications-modal__item[data-notification-id="${selectedNotificationId}"]`, + ); - menuInstance.closeMenu(); notification.remove(); getNotificationsStatus(); } else { @@ -245,15 +246,26 @@ const attachActionsListeners = () => { const attachListener = (node, callback) => node.addEventListener('click', callback, false); const markAsButtons = doc.querySelectorAll('.ibexa-notifications-modal--mark-as'); - const deleteButtons = doc.querySelectorAll('.ibexa-notifications-modal--delete'); + const deleteButtons = doc.querySelectorAll('.ibexa-notifications-open-modal-button'); + const confirmDeleteButton = doc.querySelector('.ibexa-notifications-modal--delete--confirm'); + const setNotificationId = ({ currentTarget }) => { + const deleteModal = doc.querySelector('.modal-backdrop.show.fade'); + controlZIndex(deleteModal, '199'); + + selectedNotificationId = currentTarget.dataset.notificationId; + }; markAsButtons.forEach((markAsButton) => { attachListener(markAsButton, handleMarkAsAction); }); deleteButtons.forEach((deleteButton) => { - attachListener(deleteButton, deleteNotification); + attachListener(deleteButton, setNotificationId); }); + + if (confirmDeleteButton) { + confirmDeleteButton.addEventListener('click', deleteNotification); + } }; const showNotificationPage = (pageHtml) => { const modalResults = panel.querySelector(SELECTOR_MODAL_RESULTS); diff --git a/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js b/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js index 8de35aa680..46fcdc1a1a 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/modal.helper.js @@ -1,5 +1,6 @@ -const controlZIndex = (container) => { +const controlZIndex = (container, ZIndexValue) => { const initialZIndex = container.style.zIndex; + container.style.zIndex = ZIndexValue; container.addEventListener('show.bs.modal', () => { container.style.zIndex = 'initial'; diff --git a/src/bundle/Resources/translations/ibexa_notifications.en.xliff b/src/bundle/Resources/translations/ibexa_notifications.en.xliff index 9d2880fe58..c93245578d 100644 --- a/src/bundle/Resources/translations/ibexa_notifications.en.xliff +++ b/src/bundle/Resources/translations/ibexa_notifications.en.xliff @@ -96,6 +96,11 @@ Mark as unread key: notification.mark_as_unread + + Are you sure you want to delete this notification? + Are you sure you want to delete this notification? + key: notification.modal.delete.confirm_message + The Content item is no longer available The Content item is no longer available diff --git a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig index cc8aac9385..6a21a30288 100644 --- a/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/notifications/list_item.html.twig @@ -47,7 +47,11 @@ {% set popup_items = popup_items|merge([{ label: 'notification.delete'|trans|desc('Delete'), - action_attr: { class: 'ibexa-notifications-modal--delete', 'data-notification-id': notification.id }, + action_attr: { + class: 'ibexa-notifications-open-modal-button', + 'data-notification-id': notification.id, + 'data-bs-toggle': 'modal', + 'data-bs-target': '#delete-notification-modal', } }]) %} {% embed '@ibexadesign/ui/component/table/table_body_row.html.twig' with { @@ -103,3 +107,14 @@ {% endembed %} {% endblock %} {% endembed %} + +{% embed '@ibexadesign/ui/modal/delete_confirmation.html.twig' with { + id: 'delete-notification-modal', + message: 'notification.modal.delete.confirm_message'|trans|desc('Are you sure you want to delete this notification?'), +} %} + {% block confirm_button %} + + {% endblock %} +{% endembed %} From 71ed035c0739ce4e5f61bb4c77135bd0628587dc Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 13 Oct 2025 14:05:38 +0200 Subject: [PATCH 3/3] ran " composer run phpstan -- --generate-baseline" to fix the failing tests --- phpstan-baseline.neon | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index bf81b30be2..13c5ccb4ca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6642,12 +6642,6 @@ parameters: count: 1 path: src/lib/Form/DataTransformer/DateIntervalTransformer.php - - - message: '#^Method Ibexa\\AdminUi\\Form\\DataTransformer\\DateIntervalTransformer\:\:transform\(\) has Symfony\\Component\\Form\\Exception\\TransformationFailedException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/lib/Form/DataTransformer/DateIntervalTransformer.php - - message: '#^Method Ibexa\\AdminUi\\Form\\DataTransformer\\DateIntervalTransformer\:\:transform\(\) has parameter \$value with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -6876,12 +6870,6 @@ parameters: count: 1 path: src/lib/Form/DataTransformer/LanguageTransformer.php - - - message: '#^Method Ibexa\\AdminUi\\Form\\DataTransformer\\LanguageTransformer\:\:transform\(\) has Symfony\\Component\\Form\\Exception\\TransformationFailedException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/lib/Form/DataTransformer/LanguageTransformer.php - - message: '#^Class Ibexa\\AdminUi\\Form\\DataTransformer\\LocationTransformer implements generic interface Symfony\\Component\\Form\\DataTransformerInterface but does not specify its types\: T, R$#' identifier: missingType.generics @@ -7104,12 +7092,6 @@ parameters: count: 1 path: src/lib/Form/DataTransformer/UserTransformer.php - - - message: '#^Method Ibexa\\AdminUi\\Form\\DataTransformer\\UserTransformer\:\:transform\(\) has Symfony\\Component\\Form\\Exception\\TransformationFailedException in PHPDoc @throws tag but it''s not thrown\.$#' - identifier: throws.unusedType - count: 1 - path: src/lib/Form/DataTransformer/UserTransformer.php - - message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#' identifier: function.alreadyNarrowedType