N°2039 - Rework view all notifications page (#617)

* N°2039 - Rework view all notifications page

* N°2039 - Replace modals with toasts

* N°2039 - Add bulk mode to view all notifications page

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Update css/backoffice/pages/_notifications.scss

* Update dictionaries/ui/application/newsroom/fr.dictionary.itop.newsroom.php

* Apply suggestions from code review

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>

* Add since in phpdoc

* Change newsroom empty notification illustration

* N°2039 - Refactor code to factorize logic

---------

Co-authored-by: Molkobain <lajarige.guillaume@free.fr>
This commit is contained in:
Stephen Abello
2024-02-28 09:22:39 +01:00
committed by GitHub
parent 940833a66f
commit e7b493dafa
30 changed files with 1267 additions and 83 deletions

View File

@@ -390,7 +390,18 @@
<is_null_allowed>false</is_null_allowed> <is_null_allowed>false</is_null_allowed>
</field> </field>
</fields> </fields>
<presentation/> <presentation>
<summary>
<items>
<item id="date">
<rank>10</rank>
</item>
<item id="message">
<rank>20</rank>
</item>
</items>
</summary>
</presentation>
<methods/> <methods/>
</class> </class>
</classes> </classes>

View File

@@ -49,3 +49,63 @@
.ibo-navigation-menu.ibo-is-active .ibo-navigation-menu--drawer{ .ibo-navigation-menu.ibo-is-active .ibo-navigation-menu--drawer{
transform: translate3d(0,0,0); transform: translate3d(0,0,0);
} }
// Toggler legacy CSS that has somehow been added to iTop 3.0 and that is now used by some extensions
// Round Toggle
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
vertical-align: baseline;
}
/* Hide default HTML checkbox */
.switch input {
display: none;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $ibo-color-secondary-600;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 15px;
width: 15px;
left: 3px;
bottom: 3px;
background-color: $ibo-color-secondary-300;
transition: .4s;
}
input:checked + .slider {
background-color: $ibo-color-primary-600;
}
input:focus + .slider {
box-shadow: 0 0 1px $ibo-color-primary-600;
}
input:checked + .slider:before {
transform: translateX(14.5px);
}
/* Rounded sliders */
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 7px;
}

View File

@@ -85,6 +85,9 @@ $ibo-panel--collapsible-toggler--margin-right: $ibo-spacing-300 !default;
$ibo-panel--collapsible-toggler--font-size: $ibo-font-size-250 !default; $ibo-panel--collapsible-toggler--font-size: $ibo-font-size-250 !default;
$ibo-panel--collapsible-toggler--color: $ibo-color-grey-700 !default; $ibo-panel--collapsible-toggler--color: $ibo-color-grey-700 !default;
$ibo-panel--is-selectable--body--after--z-index: $ibo-panel--header--z-index + 1 !default;
$ibo-panel--is-selectable--body--after--font-size: $ibo-font-size-700 !default;
/* Rules */ /* Rules */
.ibo-panel { .ibo-panel {
--ibo-main-color: #{map-get($ibo-panel-colors, 'neutral')}; /* --ibo-main-color is to allow overload from custom dynamic value from the DM. The overload will be done through an additional CSS class of a particular DM class or DM attribute */ --ibo-main-color: #{map-get($ibo-panel-colors, 'neutral')}; /* --ibo-main-color is to allow overload from custom dynamic value from the DM. The overload will be done through an additional CSS class of a particular DM class or DM attribute */
@@ -128,6 +131,30 @@ $ibo-panel--collapsible-toggler--color: $ibo-color-grey-700 !default;
} }
} }
} }
&.ibo-is-selectable .ibo-panel--body::after {
@include ibo-selectable;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
z-index: $ibo-panel--is-selectable--body--after--z-index;
font-size: $ibo-panel--is-selectable--body--after--font-size;
}
&.ibo-is-selectable:hover .ibo-panel--body::after {
@include ibo-selectable-hover;
display: flex;
}
&.ibo-is-selected .ibo-panel--body::after {
@include ibo-selected;
display: flex;
}
&.ibo-is-selected:hover .ibo-panel--body::after {
@include ibo-selected-hover;
display: flex;
}
} }
.ibo-panel--header { .ibo-panel--header {

View File

@@ -16,3 +16,4 @@
@import "input-one-way-password"; @import "input-one-way-password";
@import "input-set"; @import "input-set";
@import "input-text"; @import "input-text";
@import "input-toggler";

View File

@@ -0,0 +1,72 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-toggler--wrapper--width: 36px !default;
$ibo-toggler--wrapper--height: 20px !default;
$ibo-toggler--slider--border-radius: $ibo-border-radius-900 !default;
$ibo-toggler--slider--background-color: $ibo-color-secondary-600 !default;
$ibo-toggler--slider--before--height: 15px !default;
$ibo-toggler--slider--before--width: 15px !default;
$ibo-toggler--slider--before--border-radius: $ibo-border-radius-full !default;
$ibo-toggler--slider--before--background-color: $ibo-color-grey-100 !default;
$ibo-toggler--slider--checked--background-color: $ibo-color-primary-600 !default;
$ibo-toggler--slider--focus--box-shadow: 0 0 1px $ibo-color-primary-600 !default;
$ibo-toggler--label--margin-left: 4px !default;
.ibo-toggler--wrapper {
position: relative;
display: inline-block;
width: $ibo-toggler--wrapper--width;
height: $ibo-toggler--wrapper--height;
vertical-align: baseline;
.ibo-toggler {
display: none;
}
}
.ibo-toggler--slider{
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: $ibo-toggler--slider--border-radius;
background-color: $ibo-toggler--slider--background-color;
transition: .4s;
}
.ibo-toggler--slider:before {
content: "";
position: absolute;
left: 3px;
bottom: 3px;
height: $ibo-toggler--slider--before--height;
width: $ibo-toggler--slider--before--width;
border-radius: $ibo-toggler--slider--before--border-radius;
background-color: $ibo-toggler--slider--before--background-color;
transition: .4s;
}
.ibo-toggler--wrapper input:checked + .ibo-toggler--slider {
background-color: $ibo-toggler--slider--checked--background-color;
}
input:focus + .ibo-toggler--slider {
box-shadow: $ibo-toggler--slider--focus--box-shadow;
}
input:checked + .ibo-toggler--slider:before {
transform: translateX(14.5px);
}
label ~ .ibo-toggler--wrapper {
margin-left: $ibo-toggler--label--margin-left;
}

View File

@@ -62,63 +62,4 @@ $ibo-top-bar--toolbar-dashboard-title--max-width: 350px !default;
@extend %ibo-full-height-content; @extend %ibo-full-height-content;
display: flex; display: flex;
align-items: center; align-items: center;
} }
// Round Toggle
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
vertical-align: baseline;
}
/* Hide default HTML checkbox */
.switch input {
display: none;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $ibo-color-secondary-600;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 15px;
width: 15px;
left: 3px;
bottom: 3px;
background-color: $ibo-color-secondary-300;
transition: .4s;
}
input:checked + .slider {
background-color: $ibo-color-primary-600;
}
input:focus + .slider {
box-shadow: 0 0 1px $ibo-color-primary-600;
}
input:checked + .slider:before {
transform: translateX(14.5px);
}
/* Rounded sliders */
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 7px;
}

View File

@@ -3,7 +3,8 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
$ibo-object-summary--header--margin-y: $ibo-panel--highlight--height!default; $ibo-object-summary--header--margin-top: $ibo-panel--highlight--height!default;
$ibo-object-summary--header--margin-bottom: $ibo-spacing-0!default;
$ibo-object-summary--header--margin-x: $ibo-spacing-0 !default; $ibo-object-summary--header--margin-x: $ibo-spacing-0 !default;
$ibo-object-summary--header--padding-y: $ibo-spacing-300 !default; $ibo-object-summary--header--padding-y: $ibo-spacing-300 !default;
@@ -51,7 +52,7 @@ $ibo-object-summary--content--attributes--code--padding-right: $ibo-spacing-500
} }
.ibo-object-summary--header{ .ibo-object-summary--header{
margin: $ibo-object-summary--header--margin-y $ibo-object-summary--header--margin-x; margin: $ibo-object-summary--header--margin-top $ibo-object-summary--header--margin-x $ibo-object-summary--header--margin-bottom $ibo-object-summary--header--margin-x;
padding: $ibo-object-summary--header--padding-y $ibo-object-summary--header--padding-x; padding: $ibo-object-summary--header--padding-y $ibo-object-summary--header--padding-x;
background-color: $ibo-object-summary--header--background-color; background-color: $ibo-object-summary--header--background-color;
border-bottom: $ibo-object-summary--header--border; border-bottom: $ibo-object-summary--header--border;

View File

@@ -16,4 +16,5 @@
@import "run-query"; @import "run-query";
@import "welcome-popup"; @import "welcome-popup";
@import "oauth.wizard"; @import "oauth.wizard";
@import "notifications";
@import "notifications-center"; @import "notifications-center";

View File

@@ -0,0 +1,66 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-notifications--view-all--container--grid-gap: $ibo-spacing-600 !default;
$ibo-notifications--view-all--container--object-summary--panel--body--max-height: unset !default;
$ibo-notifications--view-all--item--unread--highlight--background-color: $ibo-color-red-600 !default;
$ibo-notifications--view-all--item--read--highlight--background-color: $ibo-color-grey-200 !default;
$ibo-notifications--view-all--container--large--grid-template-columns: repeat(3, 1fr) !default;
$ibo-notifications--view-all--container--medium--grid-template-columns: repeat(2, 1fr) !default;
$ibo-notifications--view-all--container--small--grid-template-columns: repeat(1, 1fr) !default;
$ibo-notifications--view-all--empty--margin-top: $ibo-spacing-950 !default;
$ibo-notifications--view-all--empty--svg--max-width: 30% !default;
.ibo-notifications--view-all--container{
display: grid;
grid-gap: $ibo-notifications--view-all--container--grid-gap;
.ibo-object-summary .ibo-panel--title{
font-size: $ibo-font-size-250;
}
.ibo-object-summary > .ibo-panel--body{
box-shadow: none;
max-height: $ibo-notifications--view-all--container--object-summary--panel--body--max-height;
}
.ibo-object-summary + .ibo-object-summary{
margin-top: 0;
}
@include mobile {
grid-template-columns: $ibo-notifications--view-all--container--small--grid-template-columns;
}
@include desktop {
grid-template-columns: $ibo-notifications--view-all--container--medium--grid-template-columns;
}
@include fullhd {
grid-template-columns: $ibo-notifications--view-all--container--large--grid-template-columns; }
}
.ibo-notifications--view-all--toolbar {
justify-content: space-between;
}
.ibo-notifications--view-all--toggler {
display: flex;
align-content: center;
}
.ibo-notifications--view-all--item--read .ibo-panel--body::before{
background-color: $ibo-notifications--view-all--item--read--highlight--background-color;
}
.ibo-notifications--view-all--item--unread .ibo-panel--body::before{
background-color: $ibo-notifications--view-all--item--unread--highlight--background-color;
}
.ibo-notifications--view-all--empty {
@extend %ibo-fully-centered-content;
flex-direction: column;
margin-top: $ibo-notifications--view-all--empty--margin-top;
svg {
max-width: $ibo-notifications--view-all--empty--svg--max-width;
height: auto;
}
}

View File

@@ -3,4 +3,5 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
@import "highlight"; @import "highlight";
@import "selectable";

View File

@@ -0,0 +1,40 @@
/*
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-selectable--background-color: transparent !default;
$ibo-selectable--hover--color: $ibo-color-grey-100 !default;
$ibo-selectable--hover--background-color: $ibo-color-grey-600 !default;
$ibo-selectable--hover--background-opacity: 0.6 !default;
$ibo-selected--color: $ibo-color-grey-100 !default;
$ibo-selected--background-color: $ibo-color-grey-900 !default;
$ibo-selected--background-opacity: 0.5 !default;
$ibo-selected--hover--background-color: $ibo-color-grey-700 !default;
$ibo-selected--hover--background-opacity: 0.5 !default;
@mixin ibo-selectable {
content: ' ';
@extend %fa-solid-base;
background-color: $ibo-selectable--background-color;
cursor: pointer;
}
@mixin ibo-selectable-hover {
@extend %fa-regular-base;
content: '\f058';
color: $ibo-selectable--hover--color;
background-color: transparentize($ibo-selectable--hover--background-color, $ibo-selectable--hover--background-opacity);
}
@mixin ibo-selected {
@extend %fa-solid-base;
content: '\f058';
color: $ibo-selected--color;
background-color: transparentize($ibo-selected--background-color, $ibo-selected--background-opacity);
}
@mixin ibo-selected-hover {
background-color: transparentize($ibo-selected--hover--background-color, $ibo-selected--hover--background-opacity);
}

View File

@@ -13,6 +13,7 @@ $ibo-spacing-600: $ibo-size-300 !default;
$ibo-spacing-700: $ibo-size-350 !default; $ibo-spacing-700: $ibo-size-350 !default;
$ibo-spacing-800: $ibo-size-400 !default; $ibo-spacing-800: $ibo-size-400 !default;
$ibo-spacing-900: $ibo-size-450 !default; $ibo-spacing-900: $ibo-size-450 !default;
$ibo-spacing-950: $ibo-size-500 !default;
:root{ :root{
--ibo-spacing-0: #{$ibo-size-0}; --ibo-spacing-0: #{$ibo-size-0};

View File

@@ -15,6 +15,9 @@ $ibo-font-size-400: 2rem !default; /* 24px */
$ibo-font-size-450: 2.5rem !default; /* 30px */ $ibo-font-size-450: 2.5rem !default; /* 30px */
$ibo-font-size-500: 3rem !default; /* 36px */ $ibo-font-size-500: 3rem !default; /* 36px */
$ibo-font-size-550: 4rem !default; /* 48px */ $ibo-font-size-550: 4rem !default; /* 48px */
$ibo-font-size-600: 5rem !default; /* 60px */
$ibo-font-size-650: 6rem !default; /* 72px */
$ibo-font-size-700: 7rem !default; /* 84px */
/* Value Common weight name (https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight) */ /* Value Common weight name (https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight) */
$ibo-font-weight-100: 100 !default; /* 100 Thin (Harline) */ $ibo-font-weight-100: 100 !default; /* 100 Thin (Harline) */

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* Copyright (C) 2013-2023 Combodo SARL * Copyright (C) 2013-2024 Combodo SARL
* *
* This file is part of iTop. * This file is part of iTop.
* *
@@ -19,5 +19,44 @@
Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array(
'UI:Newsroom:iTopNotification:Label' => ITOP_APPLICATION_SHORT, 'UI:Newsroom:iTopNotification:Label' => ITOP_APPLICATION_SHORT,
'UI:Newsroom:iTopNotification:ViewAllPage:Title' => ITOP_APPLICATION_SHORT.' notifications', 'UI:Newsroom:iTopNotification:ViewAllPage:Title' => 'Your ' . ITOP_APPLICATION_SHORT.' notifications',
'UI:Newsroom:iTopNotification:ViewAllPage:Read:Label' => 'Read',
'UI:Newsroom:iTopNotification:ViewAllPage:Unread:Label' => 'Unread',
'UI:Newsroom:iTopNotification:SelectMode:Label' => 'Select mode',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsRead:Label' => 'Mark all as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsUnread:Label' => 'Mark all as unread',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Label' => 'Delete all',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Success:Message' => 'All %1$s notifications have been deleted',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Title' => 'Delete all notifications',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Message' => 'Are you sure you want to delete all notifications?',
'UI:Newsroom:iTopNotification:ViewAllPage:Empty:Title' => 'No notification, you are up to date!',
// Actions
// - Unitary buttons
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label' => 'Delete this notification',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:ViewObject:Label' => 'Go to the notification url',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label' => 'Mark as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label' => 'Mark as unread',
// - Bulk buttons
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsRead:Label' => 'Mark selected as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsUnread:Label' => 'Mark selected as unread',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Label' => 'Delete selected',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Title' => 'Delete selected notifications',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Message' => 'Are you sure you want to delete selected notifications?',
// Feedback messages
'UI:Newsroom:iTopNotification:ViewAllPage:Action:InvalidAction:Message' => 'Invalid action: "%1$s"',
// - Mark as read
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:NoEvent:Message' => 'No notification to mark as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Success:Message' => 'The notification has been marked as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsReadMultiple:Success:Message' => '%1$s notifications have been marked as read',
// - Mark as unread
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:NoEvent:Message' => 'No notification to mark as read',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Success:Message' => 'The notification has been marked as unread',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnreadMultiple:Success:Message' => '%1$s notifications have been marked as unread',
// Delete
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:NoEvent:Message' => 'No notification to delete',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Success:Message' => 'The notification has been deleted',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteMultiple:Success:Message' => '%1$s notifications have been deleted',
)); ));

View File

@@ -0,0 +1,62 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Dict::Add('FR FR', 'French', 'Français', array(
'UI:Newsroom:iTopNotification:Label' => ITOP_APPLICATION_SHORT,
'UI:Newsroom:iTopNotification:ViewAllPage:Title' => 'Vos notifications ' . ITOP_APPLICATION_SHORT,
'UI:Newsroom:iTopNotification:ViewAllPage:Read:Label' => 'Lue',
'UI:Newsroom:iTopNotification:ViewAllPage:Unread:Label' => 'Non lue',
'UI:Newsroom:iTopNotification:SelectMode:Label' => 'Sélection multiple',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsRead:Label' => 'Marquer tout comme lu',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsUnread:Label' => 'Marquer tout comme non lu',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Label' => 'Supprimer tout',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Success:Message' => '%1$s notifications ont été supprimées',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Title' => 'Supprimer toutes les notifications',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Message' => 'Êtes-vous sûr de vouloir supprimer toutes les notifications ?',
'UI:Newsroom:iTopNotification:ViewAllPage:Empty:Title' => 'Aucune notification, vous êtes à jour !',
// Actions
// - Unitary buttons
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label' => 'Supprimer cette notification',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:ViewObject:Label' => 'Aller à l\'url de la notification',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label' => 'Marquer comme lu',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label' => 'Marquer comme non lu',
// - Bulk buttons
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsRead:Label' => 'Marquer sélectionnée(s) comme lu',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsUnread:Label' => 'Marquer sélectionnée(s) comme non lu',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Label' => 'Supprimer sélectionnée(s)',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Title' => 'Supprimer les notifications sélectionnées',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Message' => 'Êtes-vous sûr de vouloir supprimer les notifications sélectionnées ?',
// Feedback messages
'UI:Newsroom:iTopNotification:ViewAllPage:Action:InvalidAction:Message' => 'Action invalide : "%1$s"',
// - Mark as read
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:NoEvent:Message' => 'Aucune notification à marquer comme lue',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Success:Message' => 'La notification a été marquée comme lue',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsReadMultiple:Success:Message' => '%1$s notifications ont été marquées comme lues',
// - Mark as unread
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:NoEvent:Message' => 'Aucune notification à marquer comme non lue',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Success:Message' => 'La notification a été marquée comme non lue',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnreadMultiple:Success:Message' => '%1$s notifications ont été marquées comme non lues',
// Delete
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:NoEvent:Message' => 'Aucune notification à supprimer',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Success:Message' => 'La notification a été supprimée',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteMultiple:Success:Message' => '%1$s notifications ont été supprimées',
));

View File

@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" width="870.4" height="598.682" viewBox="0 0 870.4 598.682" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Group_14" data-name="Group 14" transform="translate(-182.787 -183.377)">
<g id="Group_9" data-name="Group 9" transform="translate(-237 346.007)">
<path id="Path_141-368" data-name="Path 141" d="M827.279,436.053H539.012a5.345,5.345,0,0,1-5.338-5.338V184.207a5.345,5.345,0,0,1,5.338-5.338H827.279a5.345,5.345,0,0,1,5.338,5.338V430.714a5.344,5.344,0,0,1-5.338,5.338ZM539.012,181a3.207,3.207,0,0,0-3.2,3.2V430.714a3.207,3.207,0,0,0,3.2,3.2H827.279a3.206,3.206,0,0,0,3.2-3.2V184.207a3.207,3.207,0,0,0-3.2-3.2Z" transform="translate(-44.905)" fill="#e6e6e6"/>
<circle id="Ellipse_19" data-name="Ellipse 19" cx="21.745" cy="21.745" r="21.745" transform="translate(517.152 370.472)" fill="#e6e6e6"/>
<path id="Path_142-369" data-name="Path 142" d="M634.024,377.72a3.624,3.624,0,0,0,0,7.248H804.88a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.905)" fill="#e6e6e6"/>
<path id="Path_143-370" data-name="Path 143" d="M634.024,399.466a3.624,3.624,0,0,0,0,7.248h73.52a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.905)" fill="#e6e6e6"/>
<path id="Path_154-371" data-name="Path 154" d="M895.768,350.074H607.5a5.344,5.344,0,0,1-5.338-5.338V218.229a5.344,5.344,0,0,1,5.338-5.338H895.768a5.344,5.344,0,0,1,5.338,5.338V344.736A5.344,5.344,0,0,1,895.768,350.074Z" transform="translate(-44.905)" fill="#6c63ff"/>
<path id="Path_155-372" data-name="Path 155" d="M666.206,256.985a3.624,3.624,0,0,0,0,7.248H837.063a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.905)" fill="#fff"/>
<path id="Path_156-373" data-name="Path 156" d="M666.206,277.985a3.624,3.624,0,0,0,0,7.248H837.063a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.905)" fill="#fff"/>
<path id="Path_157-374" data-name="Path 157" d="M666.206,298.731a3.624,3.624,0,0,0,0,7.248h73.52a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.905)" fill="#fff"/>
</g>
<rect id="Rectangle_1" data-name="Rectangle 1" width="870.4" height="2" transform="translate(182.787 779.636)" fill="#3f3d56"/>
<g id="Group_10" data-name="Group 10" transform="translate(100 164.584)">
<path id="Path_138-375" data-name="Path 138" d="M883.279,615.053H595.012a5.345,5.345,0,0,1-5.338-5.338V538.207a5.345,5.345,0,0,1,5.338-5.338H883.279a5.345,5.345,0,0,1,5.338,5.338v71.507A5.345,5.345,0,0,1,883.279,615.053ZM595.012,535a3.207,3.207,0,0,0-3.2,3.2v71.507a3.207,3.207,0,0,0,3.2,3.2H883.279a3.206,3.206,0,0,0,3.2-3.2V538.207a3.206,3.206,0,0,0-3.2-3.2Z" transform="translate(-44.906)" fill="#e6e6e6"/>
<circle id="Ellipse_18" data-name="Ellipse 18" cx="21.745" cy="21.745" r="21.745" transform="translate(573.152 549.472)" fill="#e6e6e6"/>
<path id="Path_139-376" data-name="Path 139" d="M690.024,556.72a3.624,3.624,0,0,0,0,7.248H860.88a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.906)" fill="#e6e6e6"/>
<path id="Path_140-377" data-name="Path 140" d="M690.024,578.466a3.624,3.624,0,0,0,0,7.248h73.52a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.906)" fill="#e6e6e6"/>
</g>
<g id="Group_11" data-name="Group 11" transform="matrix(0.914, 0.407, -0.407, 0.914, 266.183, -179.59)">
<path id="Path_135-378" data-name="Path 135" d="M811.279,721.053H523.012a5.345,5.345,0,0,1-5.338-5.338V644.207a5.344,5.344,0,0,1,5.338-5.338H811.279a5.345,5.345,0,0,1,5.338,5.338v71.507a5.345,5.345,0,0,1-5.338,5.338ZM523.012,641a3.207,3.207,0,0,0-3.2,3.2v71.507a3.207,3.207,0,0,0,3.2,3.2H811.279a3.206,3.206,0,0,0,3.2-3.2V644.207a3.207,3.207,0,0,0-3.2-3.2Z" transform="translate(112.094 -174.499)" fill="#e6e6e6"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="21.745" cy="21.745" r="21.745" transform="translate(658.152 480.973)" fill="#e6e6e6"/>
<path id="Path_136-379" data-name="Path 136" d="M618.024,662.72a3.624,3.624,0,0,0,0,7.248H788.88a3.624,3.624,0,1,0,0-7.248Z" transform="translate(112.094 -174.499)" fill="#e6e6e6"/>
<path id="Path_137-380" data-name="Path 137" d="M618.024,684.466a3.624,3.624,0,1,0,0,7.248h73.52a3.624,3.624,0,0,0,0-7.248Z" transform="translate(112.094 -174.499)" fill="#e6e6e6"/>
</g>
<g id="Group_12" data-name="Group 12" transform="matrix(0.996, -0.087, 0.087, 0.996, -217.382, -42.579)">
<path id="Path_138-2-381" data-name="Path 138" d="M883.279,615.053H595.012a5.345,5.345,0,0,1-5.338-5.338V538.207a5.345,5.345,0,0,1,5.338-5.338H883.279a5.345,5.345,0,0,1,5.338,5.338v71.507A5.345,5.345,0,0,1,883.279,615.053ZM595.012,535a3.207,3.207,0,0,0-3.2,3.2v71.507a3.207,3.207,0,0,0,3.2,3.2H883.279a3.206,3.206,0,0,0,3.2-3.2V538.207a3.206,3.206,0,0,0-3.2-3.2Z" transform="translate(-44.906)" fill="#e6e6e6"/>
<circle id="Ellipse_18-2" data-name="Ellipse 18" cx="21.745" cy="21.745" r="21.745" transform="translate(573.152 549.472)" fill="#e6e6e6"/>
<path id="Path_139-2-382" data-name="Path 139" d="M690.024,556.72a3.624,3.624,0,0,0,0,7.248H860.88a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.906)" fill="#e6e6e6"/>
<path id="Path_140-2-383" data-name="Path 140" d="M690.024,578.466a3.624,3.624,0,0,0,0,7.248h73.52a3.624,3.624,0,0,0,0-7.248Z" transform="translate(-44.906)" fill="#e6e6e6"/>
</g>
<g id="Group_13" data-name="Group 13" transform="translate(-1676 -1453.057)">
<circle id="Ellipse_21" data-name="Ellipse 21" cx="33" cy="33" r="33" transform="translate(2259.759 1653.646)" fill="#2f2e41"/>
<circle id="Ellipse_22" data-name="Ellipse 22" cx="24.561" cy="24.561" r="24.561" transform="translate(2268.868 1673.439)" fill="#feb8b8"/>
<path id="Path_159-384" data-name="Path 159" d="M902.978,629.758s-45-8-74,2l8.5,67.5-2,11,60-2-4-11Z" transform="translate(1427.781 1103.888)" fill="#feb8b8"/>
<path id="Path_160-385" data-name="Path 160" d="M871.707,702.6a302.545,302.545,0,0,1-31-1.964,8.492,8.492,0,0,1-7.561-8.645l.73-31.567.085-.123c8.694-12.593,4.459-21.791,1.2-26.743a6.4,6.4,0,0,1-1.052-3.522h0c-.5-.7-.612.405-.3-.379a2.51,2.51,0,0,1,1.836-1.538l4.964-.992a2.5,2.5,0,0,1,2.858,1.651c1.668,4.977,8.169,20.991,23.066,20.987,12.445,0,17.089-17.611,17.367-21.681a2.5,2.5,0,0,1,2.493-2.322h6.2a2.617,2.617,0,0,1,2.066.934,2.48,2.48,0,0,1,.244,2.751c-7.076,13.01,3.265,33.6,3.37,33.8l.066.13-2.918,30.293a8.541,8.541,0,0,1-6.574,7.477C884.336,702.206,878.239,702.6,871.707,702.6Z" transform="translate(1427.781 1103.888)" fill="#ccc"/>
<path id="Path_161-386" data-name="Path 161" d="M814.6,772.888c-6.72,0-12.872-.74-17.76-2.489-5.81-2.079-9.458-5.441-10.844-10a13.2,13.2,0,0,1,1.606-11.673c8.595-12.87,38.421-16.5,42.423-16.929l2.12-17.667a8.531,8.531,0,0,1,8.307-7.486l50.232-.784a8.421,8.421,0,0,1,8.5,6.977l2.854,15.7L869.1,744.512l4.98,13.945-.325.217C864.115,765.1,836.392,772.887,814.6,772.888Z" transform="translate(1427.781 1103.888)" fill="#2f2e41"/>
<path id="Path_162-387" data-name="Path 162" d="M897.478,726.258s46-6,43,18-38,41-91,0l6-12s30,8,42,2Z" transform="translate(1427.781 1103.888)" fill="#2f2e41"/>
<path id="Path_163-388" data-name="Path 163" d="M856.478,735.258s0-12-10-11-28,2-28,2-6,4,2,6,32,12,32,12Z" transform="translate(1427.781 1103.888)" fill="#feb8b8"/>
<path id="Path_164-389" data-name="Path 164" d="M873.478,733.258s0-12,10-11,28,2,28,2,6,4-2,6-32,12-32,12Z" transform="translate(1427.781 1103.888)" fill="#feb8b8"/>
<path id="Path_165-390" data-name="Path 165" d="M933.4,730.985l-14.921-51.727s-3-18-6-35-14-15-14-15l-2,1v27l2,25,19.965,54.9a10.5,10.5,0,1,0,14.956-6.177Z" transform="translate(1427.781 1103.888)" fill="#feb8b8"/>
<path id="Path_166-391" data-name="Path 166" d="M832.478,631.258s-11-2-14,15-6,35-6,35l-14.921,51.727a10.5,10.5,0,1,0,14.956,6.177l19.965-54.9,2-25V634.494a3.618,3.618,0,0,0-2-3.236Z" transform="translate(1427.781 1103.888)" fill="#feb8b8"/>
<path id="Path_167-392" data-name="Path 167" d="M841.335,579.024a73.043,73.043,0,0,0,31.6,10.412l-3.331-3.991a24.48,24.48,0,0,0,7.561,1.5,8.281,8.281,0,0,0,6.75-3.159,7.7,7.7,0,0,0,.516-7.115,14.588,14.588,0,0,0-4.589-5.739,27.323,27.323,0,0,0-25.431-4.545,16.33,16.33,0,0,0-7.6,4.872,9.236,9.236,0,0,0-1.863,8.561" transform="translate(1427.781 1103.888)" fill="#2f2e41"/>
<path id="Path_168-393" data-name="Path 168" d="M868.084,553.421a75.485,75.485,0,0,0-27.463-17.759c-6.639-2.459-13.865-3.979-20.805-2.582s-13.5,6.2-15.44,13.008c-1.583,5.568.052,11.564,2.509,16.806s5.738,10.1,7.725,15.54A35.468,35.468,0,0,1,778.92,626c6.819.914,13.105,4.119,19.771,5.825s14.533,1.59,19.486-3.185c5.241-5.052,5.346-13.267,5.092-20.542l-1.13-32.445c-.192-5.515-.356-11.208,1.633-16.356s6.716-9.656,12.235-9.609c4.183.036,7.884,2.569,11.239,5.068s6.9,5.165,11.071,5.536,8.923-2.711,8.611-6.883" transform="translate(1427.781 1103.888)" fill="#2f2e41"/>
</g>
<path id="Path_133-394" data-name="Path 133" d="M318.432,675.924a158.394,158.394,0,0,1-7.4,43.785c-.1.329-.211.653-.319.982H283.1c.029-.295.059-.624.088-.982,1.841-21.166-8.677-148.453-21.369-170.483C262.931,551.013,320.573,608.67,318.432,675.924Z" transform="translate(697.642 58.945)" fill="#e6e6e6"/>
<path id="Path_134-395" data-name="Path 134" d="M284.933,719.709c-.231.329-.471.658-.717.982H263.5c.157-.28.339-.609.55-.982,3.422-6.176,13.551-24.642,22.953-43.785,10.1-20.572,19.374-41.924,18.593-49.652C305.838,628.014,312.83,681.148,284.933,719.709Z" transform="translate(729.069 58.945)" fill="#e6e6e6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,35 @@
$('body').on('change', '.ibo-toggler', function() {
$('.ibo-notifications--view-all--bulk-buttons').toggleClass('ibo-is-hidden');
$('.ibo-object-summary').toggleClass('ibo-is-selectable').removeClass('ibo-is-selected');
});
$('body').on('click', '.ibo-object-summary.ibo-is-selectable', function() {
$(this).toggleClass('ibo-is-selected');
});
$('body').on('itop.notification.deleted', '.ibo-notifications--view-all--container', function() {
if($(this).find('.ibo-object-summary').length === 0) {
$('.ibo-notifications--view-all--empty').removeClass('ibo-is-hidden');
$('.ibo-notifications--view-all--container').addClass('ibo-is-hidden');
$('.ibo-notifications--view-all--read-action').attr('disabled', 'disabled');
$('.ibo-notifications--view-all--unread-action').attr('disabled', 'disabled');
$('.ibo-notifications--view-all--delete-action').attr('disabled', 'disabled');
}
});
let fReadUnreadDisabled = function() {
if($('.ibo-object-summary.ibo-notifications--view-all--item--unread').length === 0) {
$('.ibo-notifications--view-all--read-action').attr('disabled', 'disabled');
$('.ibo-notifications--view-all--unread-action').removeAttr('disabled');
} else if ($('.ibo-object-summary.ibo-notifications--view-all--item--read').length === 0) {
$('.ibo-notifications--view-all--read-action').removeAttr('disabled');
$('.ibo-notifications--view-all--unread-action').attr('disabled', 'disabled');
} else {
$('.ibo-notifications--view-all--read-action').removeAttr('disabled');
$('.ibo-notifications--view-all--unread-action').removeAttr('disabled');
}
}
$('body').on('itop.notification.read itop.notification.unread', '.ibo-notifications--view-all--container', fReadUnreadDisabled);
$('body').on('itop.notification.unread', '.ibo-notifications--view-all--container', fReadUnreadDisabled);

View File

@@ -265,6 +265,7 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/Set.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/Set.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => $baseDir . '/sources/Application/UI/Base/Component/Input/TextArea.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => $baseDir . '/sources/Application/UI/Base/Component/Input/TextArea.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Toggler' => $baseDir . '/sources/Application/UI/Base/Component/Input/Toggler.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => $baseDir . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => $baseDir . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php',

View File

@@ -640,6 +640,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/Set.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/Set.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/TextArea.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/TextArea.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Toggler' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Toggler.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php',

View File

@@ -38,14 +38,8 @@ class ButtonGroup extends UIBlock
/** /**
* Button constructor. * Button constructor.
* *
* @param string $sLabel * @param array $aButtons
* @param string|null $sId * @param string|null $sId
* @param string $sTooltip
* @param string $sIconClass
* @param string $sActionType
* @param string $sColor
* @param string $sJsCode
* @param string $sOnClickJsCode
*/ */
public function __construct(array $aButtons = [], ?string $sId = null) public function __construct(array $aButtons = [], ?string $sId = null)
{ {

View File

@@ -95,10 +95,11 @@ class InputUIBlockFactory extends AbstractUIBlockFactory
* @param string $sLabel * @param string $sLabel
* @param \Combodo\iTop\Application\UI\Base\Component\Input\Input $oInput * @param \Combodo\iTop\Application\UI\Base\Component\Input\Input $oInput
* @param string|null $sId * @param string|null $sId
* @since 3.2.0 method is now public
* *
* @return \Combodo\iTop\Application\UI\Base\Component\Input\InputWithLabel * @return \Combodo\iTop\Application\UI\Base\Component\Input\InputWithLabel
*/ */
private static function MakeInputWithLabel(string $sName, string $sLabel, Input $oInput, ?string $sId = null) public static function MakeInputWithLabel(string $sName, string $sLabel, Input $oInput, ?string $sId = null)
{ {
$oInput->SetName($sName); $oInput->SetName($sName);

View File

@@ -138,4 +138,8 @@ class InputWithLabel extends UIBlock
return utils::IsNotNullOrEmptyString($this->sDescription); return utils::IsNotNullOrEmptyString($this->sDescription);
} }
public function GetSubBlocks(): array
{
return [$this->oInput->GetId() => $this->oInput];
}
} }

View File

@@ -0,0 +1,37 @@
<?php
/**
* @copyright Copyright (C) 2010-2024 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input;
/**
* @package Combodo\iTop\Application\UI\Base\Component\Input
* @since 3.2.0
*/
class Toggler extends Input {
// Overloaded constants
public const BLOCK_CODE = 'ibo-toggler';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/input/input-toggler';
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/input/input-toggler';
public function __construct(?string $sId = null)
{
parent::__construct($sId);
$this->SetType('checkbox');
}
public function SetIsToggled(bool $bIsToggled): static
{
return $this->SetIsChecked($bIsToggled);
}
public function IsToggled(): bool
{
return $this->IsChecked();
}
}

View File

@@ -87,7 +87,7 @@ class NewsroomMenuFactory
$sPlaceholderImageUrl= 'far fa-envelope'; $sPlaceholderImageUrl= 'far fa-envelope';
$aParams = array( $aParams = array(
'image_icon' => $sImageUrl, 'image_icon' => $sImageUrl,
'no_message_icon' => file_get_contents(APPROOT.'images/illustrations/undraw_empty.svg'), 'no_message_icon' => file_get_contents(APPROOT.'images/illustrations/undraw_social_serenity.svg'),
'placeholder_image_icon' => $sPlaceholderImageUrl, 'placeholder_image_icon' => $sPlaceholderImageUrl,
'cache_uuid' => 'itop-newsroom-'.UserRights::GetUserId().'-'.md5(APPROOT), 'cache_uuid' => 'itop-newsroom-'.UserRights::GetUserId().'-'.md5(APPROOT),
'providers' => $aProviderParams, 'providers' => $aProviderParams,

View File

@@ -103,6 +103,17 @@ class ObjectDetails extends Panel implements iKeyboardShortcut
return $this->sClassName; return $this->sClassName;
} }
/**
* @see self::$sClassLabel
* @return $this
*/
public function SetClassLabel($sClassLabel)
{
$this->sClassLabel = $sClassLabel;
return $this;
}
/** /**
* @see self::$sClassLabel * @see self::$sClassLabel
* @return string * @return string

View File

@@ -102,8 +102,8 @@ class ObjectSummary extends ObjectDetails
{ {
$oRouter = Router::GetInstance(); $oRouter = Router::GetInstance();
$oDetailsButton = null; $oDetailsButton = null;
if(UserRights::IsActionAllowed($this->sClassName, UR_ACTION_MODIFY)) { // We can pass a DBObject to the UIBlock, so we check for the DisplayModifyForm method
$sRootUrl = utils::GetAbsoluteUrlAppRoot(); if(method_exists($this->oObject, 'DisplayModifyForm') && UserRights::IsActionAllowed($this->sClassName, UR_ACTION_MODIFY)) {
$oPopoverMenu = new PopoverMenu(); $oPopoverMenu = new PopoverMenu();
$oDetailsAction = new URLPopupMenuItem( $oDetailsAction = new URLPopupMenuItem(

View File

@@ -5,15 +5,33 @@ namespace Combodo\iTop\Controller\Newsroom;
use ArchivedObjectException; use ArchivedObjectException;
use Combodo\iTop\Application\Branding; use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\TwigBase\Controller\Controller; use Combodo\iTop\Application\TwigBase\Controller\Controller;
use Combodo\iTop\Application\UI\Base\Component\Button\Button;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Toggler;
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\WebPage\iTopWebPage; use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\JsonPage;
use Combodo\iTop\Application\WebPage\JsonPPage; use Combodo\iTop\Application\WebPage\JsonPPage;
use Combodo\iTop\Service\Notification\NotificationsRepository;
use Combodo\iTop\Service\Router\Router; use Combodo\iTop\Service\Router\Router;
use CoreException; use CoreException;
use DBObjectSearch; use DBObjectSearch;
use DBObjectSet; use DBObjectSet;
use Dict; use Dict;
use DisplayBlock; use JSPopupMenuItem;
use MetaModel; use MetaModel;
use SecurityException;
use URLPopupMenuItem;
use UserRights; use UserRights;
use utils; use utils;
@@ -38,12 +56,476 @@ class iTopNewsroomController extends Controller
public function OperationViewAll() public function OperationViewAll()
{ {
$oPage = new iTopWebPage(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title')); $oPage = new iTopWebPage(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title'));
$oSearch = DBObjectSearch::FromOQL('SELECT EventiTopNotification WHERE read = "no"'); $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice/itop-newsroom.view-all.js');
$oSearch->AddCondition('contact_id', UserRights::GetContactId(), '='); // Add title block
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, []); // Make bulk actions block
$oBlock->Display($oPage, 0); $oBulkActionsBlock = PanelUIBlockFactory::MakeForInformation(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title'));
$oPage->add("<div class='sf_results_area ibo-add-margin-top-250' data-target='search_results'>"); $oToolbar = ToolbarUIBlockFactory::MakeStandard();
$oToolbar->AddCSSClass('ibo-notifications--view-all--toolbar');
$oAllModeButtonsContainer = new UIContentBlock('ibo-notifications--view-all--all-mode-buttons', ['ibo-notifications--view-all--bulk-buttons', 'ibo-notifications--view-all--all-mode-buttons']);
// Create CSRF token we'll use in this page
$sCSRFToken = utils::GetNewTransactionId();
// Make button to mark all as read
$sMarkMultipleAsReadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_multiple_as_read', ['token' => $sCSRFToken]);
$sMarkMultipleAsUnreadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_multiple_as_unread', ['token' => $sCSRFToken]);
$sDeleteMultipleUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.delete_multiple', ['token' => $sCSRFToken]);
$oMarkAllAsReadButton = ButtonUIBlockFactory::MakeForSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsRead:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsRead:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsRead:Label'
);
$oMarkAllAsReadButton->SetIconClass('far fa-envelope-open')
->AddCSSClass('ibo-notifications--view-all--read-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToMarkAsRead = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"].ibo-notifications--view-all--item--unread');
let aNotificationIds = [];
oNotificationToMarkAsRead.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
$.ajax({
url: '{$sMarkMultipleAsReadUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
let MarkAsReadButton = oNotificationToMarkAsRead.find('.ibo-button-group:not(.ibo-is-hidden)');
let MarkAsUnreadButton = oNotificationToMarkAsRead.find('.ibo-button-group.ibo-is-hidden');
MarkAsReadButton.addClass('ibo-is-hidden');
MarkAsUnreadButton.removeClass('ibo-is-hidden');
oNotificationToMarkAsRead.removeClass('ibo-notifications--view-all--item--unread').addClass('ibo-notifications--view-all--item--read');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.read');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
// Make button to mark all as unread
$oMarkAllAsUnreadButton = ButtonUIBlockFactory::MakeForSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsUnread:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsUnread:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAllAsUnread:Label',
);
$oMarkAllAsUnreadButton->SetIconClass('far fa-envelope')
->AddCSSClass('ibo-notifications--view-all--unread-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToMarkAsUnread = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"].ibo-notifications--view-all--item--read');
let aNotificationIds = [];
oNotificationToMarkAsUnread.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
$.ajax({
url: '{$sMarkMultipleAsUnreadUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
let MarkAsUnreadButton = oNotificationToMarkAsUnread.find('.ibo-button-group:not(.ibo-is-hidden)');
let MarkAsReadButton = oNotificationToMarkAsUnread.find('.ibo-button-group.ibo-is-hidden');
MarkAsReadButton.removeClass('ibo-is-hidden');
MarkAsUnreadButton.addClass('ibo-is-hidden');
oNotificationToMarkAsUnread.removeClass('ibo-notifications--view-all--item--read').addClass('ibo-notifications--view-all--item--unread');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.unread');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
// Make button to delete all
$oDeleteAllButton = ButtonUIBlockFactory::MakeForDestructiveAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Label'
);
$oDeleteAllButtonConfirmTitle = Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Title');
$oDeleteAllButtonConfirmMessage = Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteAll:Confirmation:Message');
$oDeleteAllButton->SetActionType(Button::ENUM_ACTION_TYPE_ALTERNATIVE);
$oDeleteAllButton->SetIconClass('fas fa-trash-alt')
->AddCSSClass('ibo-notifications--view-all--delete-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToDelete = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"]');
let aNotificationIds = [];
oNotificationToDelete.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
CombodoModal.OpenConfirmationModal({
title: '$oDeleteAllButtonConfirmTitle',
content: '$oDeleteAllButtonConfirmMessage',
callback_on_confirm: function() {
$.ajax({
url: '{$sDeleteMultipleUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
oNotificationToDelete.remove();
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.deleted');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
},
buttons: {
confirm: {
text: '$oDeleteAllButtonConfirmTitle',
classes: ['ibo-is-danger']
}
},
do_not_show_again_pref_key: 'notifications-center.delete-all-confirmation-modal.do-not-show-again',
}, []);
JS
);
// Add "all" buttons to their container
$oAllModeButtonsContainer->AddSubBlock($oMarkAllAsReadButton);
$oAllModeButtonsContainer->AddSubBlock($oMarkAllAsUnreadButton);
$oAllModeButtonsContainer->AddSubBlock($oDeleteAllButton);
$oToolbar->AddSubBlock($oAllModeButtonsContainer);
$oSelectedModelButtonsContainer = new UIContentBlock('ibo-notifications--view-all--selected-mode-buttons', ['ibo-is-hidden', 'ibo-notifications--view-all--bulk-buttons', 'ibo-notifications--view-all--selected-mode-buttons']);
// Make button mark all selected as read
$oMarkSelectedAsReadButton = ButtonUIBlockFactory::MakeForSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsRead:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsRead:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsRead:Label'
);
$oMarkSelectedAsReadButton->SetIconClass('far fa-envelope-open')
->AddCSSClass('ibo-notifications--view-all--read-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToMarkAsRead = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"].ibo-notifications--view-all--item--unread.ibo-is-selected');
let aNotificationIds = [];
oNotificationToMarkAsRead.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
$.ajax({
url: '{$sMarkMultipleAsReadUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
let MarkAsReadButton = oNotificationToMarkAsRead.find('.ibo-button-group:not(.ibo-is-hidden)');
let MarkAsUnreadButton = oNotificationToMarkAsRead.find('.ibo-button-group.ibo-is-hidden');
MarkAsReadButton.addClass('ibo-is-hidden');
MarkAsUnreadButton.removeClass('ibo-is-hidden');
oNotificationToMarkAsRead.removeClass('ibo-notifications--view-all--item--unread').addClass('ibo-notifications--view-all--item--read');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.read');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
// Make button mark all selected as unread
$oMarkSelectedAsUnreadButton = ButtonUIBlockFactory::MakeForSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsUnread:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsUnread:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkSelectedAsUnread:Label'
);
$oMarkSelectedAsUnreadButton->SetIconClass('far fa-envelope')
->AddCSSClass('ibo-notifications--view-all--unread-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToMarkAsUnread = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"].ibo-notifications--view-all--item--read.ibo-is-selected');
let aNotificationIds = [];
oNotificationToMarkAsUnread.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
$.ajax({
url: '{$sMarkMultipleAsUnreadUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
let MarkAsUnreadButton = oNotificationToMarkAsUnread.find('.ibo-button-group:not(.ibo-is-hidden)');
let MarkAsReadButton = oNotificationToMarkAsUnread.find('.ibo-button-group.ibo-is-hidden');
MarkAsReadButton.removeClass('ibo-is-hidden');
MarkAsUnreadButton.addClass('ibo-is-hidden');
oNotificationToMarkAsUnread.removeClass('ibo-notifications--view-all--item--read').addClass('ibo-notifications--view-all--item--unread');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.unread');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
// Make button delete all selected
$oDeleteSelectedButton = ButtonUIBlockFactory::MakeForDestructiveAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Label'
);
$oDeleteSelectedButtonConfirmTitle = Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Title');
$oDeleteSelectedButtonConfirmMessage = Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:DeleteSelected:Confirmation:Message');
$oDeleteSelectedButton->SetActionType(Button::ENUM_ACTION_TYPE_ALTERNATIVE);
$oDeleteSelectedButton->SetIconClass('fas fa-trash-alt')
->AddCSSClass('ibo-notifications--view-all--delete-action')
->SetOnClickJsCode(
<<<JS
let oSelf = this;
let oNotificationToDelete = $('.ibo-notifications--view-all--container [data-role="ibo-object-summary"].ibo-is-selected');
let aNotificationIds = [];
oNotificationToDelete.each(function(){
aNotificationIds.push($(this).attr('data-object-id'));
});
CombodoModal.OpenConfirmationModal({
title: '$oDeleteSelectedButtonConfirmTitle',
content: '$oDeleteSelectedButtonConfirmMessage',
callback_on_confirm: function() {
$.ajax({
url: '{$sDeleteMultipleUrl}',
data: {
notification_ids: aNotificationIds
},
type: 'POST',
success: function(data) {
if (data.status === 'success') {
oNotificationToDelete.remove();
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.deleted');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
},
buttons: {
confirm: {
text: '$oDeleteSelectedButtonConfirmTitle',
classes: ['ibo-is-danger']
}
},
do_not_show_again_pref_key: 'notifications-center.delete-all-confirmation-modal.do-not-show-again',
}, []);
JS
);
// Add "selected" buttons to their container
$oSelectedModelButtonsContainer->AddSubBlock($oMarkSelectedAsReadButton);
$oSelectedModelButtonsContainer->AddSubBlock($oMarkSelectedAsUnreadButton);
$oSelectedModelButtonsContainer->AddSubBlock($oDeleteSelectedButton);
$oToolbar->AddSubBlock($oSelectedModelButtonsContainer);
// Make toggler to switch between "all" and "selected" mode
$oTogglerContentBlock = new UIContentBlock('ibo-notifications--view-all--toggler', ['ibo-notifications--view-all--toggler']);
$oToggler = new Toggler();
$oInputWithLabel = InputUIBlockFactory::MakeInputWithLabel('slider', Dict::S('UI:Newsroom:iTopNotification:SelectMode:Label'), $oToggler);
$oTogglerContentBlock->AddSubBlock($oInputWithLabel);
$oToolbar->AddSubBlock($oTogglerContentBlock);
$oBulkActionsBlock->AddSubBlock($oToolbar);
$oPage->AddUiBlock($oBulkActionsBlock);
// Search for all notifications for the current user
$oSearch = DBObjectSearch::FromOQL('SELECT EventiTopNotification');
$oSearch->AddCondition('contact_id', UserRights::GetContactId(), '=');
$oSet = new DBObjectSet($oSearch, array('read' => true, 'date' => true), array());
// Add main content block
$oMainContentBlock = new UIContentBlock(null, ['ibo-notifications--view-all--container']);
$oPage->AddUiBlock($oMainContentBlock);
while ($oEvent = $oSet->Fetch()) {
$iEventId = $oEvent->GetKey();
// Prepare object summary block
$sReadColor = $oEvent->Get('read') === 'no' ? 'ibo-notifications--view-all--item--unread' : 'ibo-notifications--view-all--item--read';
$sReadLabel = $oEvent->Get('read') === 'no' ? Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Unread:Label') : Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Read:Label');
$oEventBlock = new ObjectSummary($oEvent);
$oEventBlock->SetCSSColorClass($sReadColor);
$oEventBlock->SetSubTitle($sReadLabel);
$oEventBlock->SetClassLabel('');
$oImage = $oEvent->Get('icon');
if (!$oImage->IsEmpty()) {
$sIconUrl = $oImage->GetDisplayURL(get_class($oEvent), $iEventId, 'icon');
$oEventBlock->SetIcon($sIconUrl, Panel::ENUM_ICON_COVER_METHOD_COVER,true);
}
// Prepare Event actions
$oMarkAsReadPopoverMenu = new PopoverMenu();
$oMarkAsUnreadPopoverMenu = new PopoverMenu();
// Common actions
$sDeleteUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.delete_event', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]);
$oDeleteButton = new JSPopupMenuItem(
'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label',
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label'),
<<<JS
let oSelf = this;
$.ajax({
url: '{$sDeleteUrl}',
type: 'POST',
success: function(data) {
if (data.status === 'success') {
$(oSelf).parents('.ibo-object-summary').remove();
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.deleted');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS,
'_blank'
);
$oViewButton = new URLPopupMenuItem(
'UI:Newsroom:iTopNotification:ViewAllPage:Action:ViewObject:Label',
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:ViewObject:Label'),
Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.view_event', ['event_id' => $oEvent->GetKey()]),
'_blank'
);
// Mark as read action
$oMarkAsReadButton = ButtonUIBlockFactory::MakeForAlternativeSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label',
);
// Mark as read action
$oMarkAsReadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oViewButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT);
$oMarkAsReadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oDeleteButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT);
// Mark as unread action
$oMarkAsUnreadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oViewButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT);
$oMarkAsUnreadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oDeleteButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT);
// Mark as unread action
$sMarkAsReadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_as_read', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]);
$oMarkAsReadButton->SetOnClickJsCode(
<<<JS
let oSelf = this;
$.ajax({
url: '{$sMarkAsReadUrl}',
type: 'POST',
success: function(data) {
if (data.status === 'success') {
$(oSelf).parent('.ibo-button-group').addClass('ibo-is-hidden');
$(oSelf).parent('.ibo-button-group').siblings('.ibo-button-group').removeClass('ibo-is-hidden');
$(oSelf).parents('.ibo-object-summary').removeClass('ibo-notifications--view-all--item--unread').addClass('ibo-notifications--view-all--item--read');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.read');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
$oMarkAsReadButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oMarkAsReadButton, $oMarkAsReadPopoverMenu);
$oMarkAsUnreadButton = ButtonUIBlockFactory::MakeForAlternativeSecondaryAction(
Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label'),
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label',
'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label'
);
$sMarkAsUnreadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_as_unread', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]);
$oMarkAsUnreadButton->SetOnClickJsCode(
<<<JS
let oSelf = this;
$.ajax({
url: '{$sMarkAsUnreadUrl}',
type: 'POST',
success: function(data) {
if (data.status === 'success') {
$(oSelf).parent('.ibo-button-group').addClass('ibo-is-hidden');
$(oSelf).parent('.ibo-button-group').siblings('.ibo-button-group').removeClass('ibo-is-hidden');
$(oSelf).parents('.ibo-object-summary').removeClass('ibo-notifications--view-all--item--read').addClass('ibo-notifications--view-all--item--unread');
CombodoToast.OpenSuccessToast(data.message);
$('.ibo-notifications--view-all--container').trigger('itop.notification.unread');
}
else {
CombodoToast.OpenErrorToast(data.message);
}
}
});
JS
);
$oMarkAsUnreadButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oMarkAsUnreadButton, $oMarkAsUnreadPopoverMenu);
// Add actions to the object summary block and remove old button
$oOldButtonId = $oEventBlock->GetActions()->GetId();
$oEventBlock->RemoveSubBlock($oOldButtonId);
$oEventBlock->SetToolBlocks([$oMarkAsReadButtonGroup, $oMarkAsUnreadButtonGroup]);
$oActionsBlock = new UIContentBlock();
$oActionsBlock->AddSubBlock($oMarkAsReadButtonGroup);
$oActionsBlock->AddSubBlock($oMarkAsUnreadButtonGroup);
$oEventBlock->SetActions($oActionsBlock);
// Display the right button depending on the read status
if($oEvent->Get('read') === 'no'){
$oMarkAsUnreadButtonGroup->SetCSSClasses(['ibo-is-hidden']);
}
else{
$oMarkAsReadButtonGroup->SetCSSClasses(['ibo-is-hidden']);
}
$oMainContentBlock->AddSubBlock($oEventBlock);
}
// Add empty content block
$oEmptyContentBlock = new UIContentBlock('ibo-notifications--view-all--empty', ['ibo-notifications--view-all--empty', 'ibo-svg-illustration--container']);
$oEmptyContentBlock->AddSubBlock(new Html(file_get_contents(APPROOT.'/images/illustrations/undraw_social_serenity.svg')));
$oEmptyContentBlock->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Empty:Title')));
$oPage->AddUiBlock($oEmptyContentBlock);
// Hide empty content block if there are notifications
if($oSet->Count() === 0){
$oMainContentBlock->AddCSSClass('ibo-is-hidden');
}
else {
$oEmptyContentBlock->AddCSSClass('ibo-is-hidden');
}
return $oPage; return $oPage;
} }
@@ -159,4 +641,163 @@ HTML;
} }
} }
} }
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationMarkAsUnread(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnSingleNotification('mark_as_unread'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationMarkAsRead(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnSingleNotification('mark_as_read'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationDeleteEvent(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnSingleNotification('delete'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationMarkMultipleAsRead(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnMultipleNotifications('mark_as_read'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationMarkMultipleAsUnread(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnMultipleNotifications('mark_as_unread'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @return \Combodo\iTop\Application\WebPage\JsonPage
*/
public function OperationDeleteMultiple(): JsonPage
{
$oPage = new JsonPage();
$oPage->SetData($this->PerformActionOnMultipleNotifications('delete'));
$oPage->SetOutputDataOnly(true);
return $oPage;
}
/**
* @param string $sAction
*
* @return string[]
* @throws \SecurityException
*/
protected function PerformActionOnSingleNotification(string $sAction): array
{
$iNotificationId = utils::ReadParam('notification_id', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
return $this->PerformAction($sAction, [$iNotificationId]);
}
/**
* @param string $sAction
*
* @return string[]
* @throws \SecurityException
*/
protected function PerformActionOnMultipleNotifications(string $sAction): array
{
$aNotificationIds = utils::ReadParam('notification_ids', []);
return $this->PerformAction($sAction, $aNotificationIds);
}
/**
* @param string $sAction
* @param array $aNotificationIds
*
* @return string[]
* @throws \SecurityException
*/
protected function PerformAction(string $sAction, array $aNotificationIds): array
{
$sCSRFToken = utils::ReadParam('token', '', false, 'raw_data');
if(utils::IsTransactionValid($sCSRFToken, false) === false){
throw new SecurityException('Invalid CSRF token');
}
$sActionAsCamelCase = utils::ToCamelCase($sAction);
$aReturnData = [
'status' => 'error',
'message' => 'Invalid notification(s)'
];
// Check action type
if (false === in_array($sAction, ['mark_as_read', 'mark_as_unread', 'delete'])) {
$aReturnData['message'] = Dict::S("UI:Newsroom:iTopNotification:ViewAllPage:Action:InvalidAction:Message");
return $aReturnData;
}
// No ID passed to the API
if (count($aNotificationIds) === 0) {
$aReturnData['message'] = Dict::S("UI:Newsroom:iTopNotification:ViewAllPage:Action:$sActionAsCamelCase:NoEvent:Message");
return $aReturnData;
}
try {
$sRepositoryMethodName = "SearchNotificationsTo{$sActionAsCamelCase}ByContact";
$oSet = NotificationsRepository::GetInstance()->$sRepositoryMethodName(UserRights::GetContactId(), $aNotificationIds);
// No notification found
$iCount = $oSet->Count();
if($iCount === 0) {
$aReturnData['message'] = Dict::S("UI:Newsroom:iTopNotification:ViewAllPage:Action:$sActionAsCamelCase:NoEvent:Message");
return $aReturnData;
}
while ($oEvent = $oSet->Fetch()) {
if ($sAction === 'mark_as_read') {
$oEvent->Set('read', 'yes');
$oEvent->SetCurrentDate('read_date');
$oEvent->DBWrite();
} elseif ($sAction === 'mark_as_unread') {
$oEvent->Set('read', 'no');
$oEvent->DBWrite();
} elseif ($sAction === 'delete') {
$oEvent->DBDelete();
}
}
$aReturnData['status'] = 'success';
if ($iCount === 1) {
$aReturnData['message'] = Dict::S("UI:Newsroom:iTopNotification:ViewAllPage:Action:{$sActionAsCamelCase}:Success:Message");
} else {
$aReturnData['message'] = Dict::Format("UI:Newsroom:iTopNotification:ViewAllPage:Action:{$sActionAsCamelCase}Multiple:Success:Message", $iCount);
}
} catch (Exception $oException) {
$aReturnData['message'] = $oException->getMessage();
}
return $aReturnData;
}
} }

View File

@@ -2,8 +2,12 @@
namespace Combodo\iTop\Service\Notification; namespace Combodo\iTop\Service\Notification;
use BinaryExpression;
use DBObjectSearch; use DBObjectSearch;
use DBObjectSet; use DBObjectSet;
use Expression;
use FieldExpression;
use VariableExpression;
/** /**
* Class NotificationsRepository * Class NotificationsRepository
@@ -44,6 +48,74 @@ class NotificationsRepository
// Don't do anything, we don't want to be initialized // Don't do anything, we don't want to be initialized
} }
/**
* @param int $iContactId ID of the contact to retrieve notifications for
* @param array $aNotificationIds Optional IDs of the notifications to retrieve, if omitted all notifications will be retrieved
*
* @return \DBObjectSet Set of notifications for $iContactId, optionally limited to $aNotificationIds
* @throws \CoreException
* @throws \OQLException
*/
public function SearchNotificationsByContact(int $iContactId, array $aNotificationIds = []): DBObjectSet
{
$oSearch = DBObjectSearch::FromOQL("SELECT EventiTopNotification WHERE contact_id = :contact_id");
$aParams = [
"contact_id" => $iContactId,
];
if (count($aNotificationIds) > 0) {
$oSearch->AddConditionExpression(Expression::FromOQL("{$oSearch->GetClassAlias()}.id IN (:notification_ids)"));
$aParams["notification_ids"] = $aNotificationIds;
}
return new DBObjectSet($oSearch, [], $aParams);
}
/**
* @param int $iContactId ID of the contact to retrieve unread notifications for
* @param array $aNotificationIds Optional IDs of the unread notifications to retrieve, if omitted all unread notifications will be retrieved
*
* @return \DBObjectSet Set of unread notifications for $iContactId, optionally limited to $aNotificationIds
* @throws \CoreException
* @throws \OQLException
*/
public function SearchNotificationsToMarkAsReadByContact(int $iContactId, array $aNotificationIds = []): DBObjectSet
{
$oSet = $this->SearchNotificationsByContact($iContactId, $aNotificationIds);
$oSet->GetFilter()->AddCondition("read", "=", "no");
return $oSet;
}
/**
* @param int $iContactId ID of the contact to retrieve read notifications for
* @param array $aNotificationIds Optional IDs of the read notifications to retrieve, if omitted all read notifications will be retrieved
*
* @return \DBObjectSet Set of read notifications for $iContactId, optionally limited to $aNotificationIds
* @throws \CoreException
* @throws \OQLException
*/
public function SearchNotificationsToMarkAsUnreadByContact(int $iContactId, array $aNotificationIds = []): DBObjectSet
{
$oSet = $this->SearchNotificationsByContact($iContactId, $aNotificationIds);
$oSet->GetFilter()->AddCondition("read", "=", "yes");
return $oSet;
}
/**
* @param int $iContactId ID of the contact to retrieve read notifications for
* @param array $aNotificationIds Optional IDs of the notifications to retrieve, if omitted all notifications will be retrieved
*
* @return \DBObjectSet Set of notifications for $iContactId, optionally limited to $aNotificationIds
* @throws \CoreException
* @throws \OQLException
*/
public function SearchNotificationsToDeleteByContact(int $iContactId, array $aNotificationIds = []): DBObjectSet
{
return $this->SearchNotificationsByContact($iContactId, $aNotificationIds);
}
/** /**
* Search for subscriptions by contact ID. * Search for subscriptions by contact ID.
* *

View File

@@ -0,0 +1,7 @@
{% extends "base/components/input/layout.html.twig" %}
{% block iboInput %}
<span class="ibo-toggler--wrapper">
{{ parent() }}
<span class="ibo-toggler--slider"></span>
</span>
{% endblock %}

View File

@@ -0,0 +1,5 @@
$('#{{ oUIBlock.GetId() }}').parent().on('click', function() {
let oInput = $(this).find('.ibo-toggler');
oInput.prop('checked', !oInput.prop('checked'));
oInput.trigger('change');
});