Merge remote-tracking branch 'origin/master' into upgrade-sf5

This commit is contained in:
2024-09-12 13:28:08 +02:00
32 changed files with 1483 additions and 620 deletions

View File

@@ -1,14 +1,16 @@
import {createApp} from "vue";
import { createApp } from "vue";
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
import NotificationReadAllToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadAllToggle.vue";
const i18n = _createI18n({});
window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll('.notification_toggle_read_status')
.forEach(function (el, i) {
createApp({
template: `<notification-read-toggle
window.addEventListener("DOMContentLoaded", function (e) {
document
.querySelectorAll(".notification_toggle_read_status")
.forEach(function (el, i) {
createApp({
template: `<notification-read-toggle
:notificationId="notificationId"
:buttonClass="buttonClass"
:buttonNoText="buttonNoText"
@@ -17,40 +19,45 @@ window.addEventListener('DOMContentLoaded', function (e) {
@markRead="onMarkRead"
@markUnread="onMarkUnread">
</notification-read-toggle>`,
components: {
NotificationReadToggle,
},
data() {
return {
notificationId: el.dataset.notificationId,
buttonClass: el.dataset.buttonClass,
buttonNoText: 'false' === el.dataset.buttonText,
showUrl: el.dataset.showButtonUrl,
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
container: el.dataset.container
}
},
computed: {
getContainer() {
return document.querySelectorAll(`div.${this.container}`);
}
},
methods: {
onMarkRead() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('read', 'unread');
} else { throw 'data-container attribute is missing' }
this.isRead = false;
},
onMarkUnread() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('unread', 'read');
} else { throw 'data-container attribute is missing' }
this.isRead = true;
},
}
})
.use(i18n)
.mount(el);
});
components: {
NotificationReadToggle,
},
data() {
return {
notificationId: parseInt(el.dataset.notificationId),
buttonClass: el.dataset.buttonClass,
buttonNoText: "false" === el.dataset.buttonText,
showUrl: el.dataset.showButtonUrl,
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
container: el.dataset.container,
};
},
computed: {
getContainer() {
return document.querySelectorAll(`div.${this.container}`);
},
},
methods: {
onMarkRead() {
if (typeof this.getContainer[i] !== "undefined") {
this.getContainer[i].classList.replace("read", "unread");
} else {
throw "data-container attribute is missing";
}
this.isRead = false;
},
onMarkUnread() {
if (typeof this.getContainer[i] !== "undefined") {
this.getContainer[i].classList.replace("unread", "read");
} else {
throw "data-container attribute is missing";
}
this.isRead = true;
},
},
})
.use(i18n)
.mount(el);
});
});

View File

@@ -0,0 +1,39 @@
import { createApp } from "vue";
import { _createI18n } from "../../vuejs/_js/i18n";
import NotificationReadAllToggle from "../../vuejs/_components/Notification/NotificationReadAllToggle.vue";
const i18n = _createI18n({});
document.addEventListener("DOMContentLoaded", function () {
const elements = document.querySelectorAll(".notification_all_read");
elements.forEach((element) => {
console.log('launch');
createApp({
template: `<notification-read-all-toggle @markAsRead="markAsRead" @markAsUnRead="markAsUnread"></notification-read-all-toggle>`,
components: {
NotificationReadAllToggle,
},
methods: {
markAsRead(id: number) {
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
if (el === null) {
return;
}
el.classList.add('read');
el.classList.remove('unread');
},
markAsUnread(id: number) {
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
if (el === null) {
return;
}
el.classList.remove('read');
el.classList.add('unread');
},
}
})
.use(i18n)
.mount(element);
});
});

View File

@@ -0,0 +1,50 @@
<template>
<div>
<button v-if="idsMarkedAsRead.length === 0"
class="btn btn-primary"
type="button"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i> Marquer tout comme lu
</button>
<button v-else
class="btn btn-primary"
type="button"
@click="undo"
>
<i class="fa fa-sm fa-envelope-open-o"></i> Annuler
</button>
</div>
</template>
<script lang="ts" setup>
import { makeFetch } from "../../../lib/api/apiMethods";
import { ref } from "vue";
const emit = defineEmits<{
(e: 'markAsRead', id: number): void,
(e: 'markAsUnRead', id: number): void,
}>();
const idsMarkedAsRead = ref([] as number[]);
async function markAllRead() {
const ids: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/allread`, null);
for (let i of ids) {
idsMarkedAsRead.value.push(i);
emit('markAsRead', i);
}
}
async function undo() {
const touched: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/undoallread`, idsMarkedAsRead.value);
while (idsMarkedAsRead.value.length > 0) {
idsMarkedAsRead.value.pop();
}
for (let t of touched) {
emit('markAsUnRead', t);
}
};
</script>
<style lang="scss" scoped></style>

View File

@@ -1,47 +1,66 @@
<template>
<div :class="{'btn-group btn-group-sm float-end': isButtonGroup }"
role="group" aria-label="Notification actions">
<button v-if="isRead"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsUnread')"
@click="markAsUnread"
>
<div
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
role="group"
aria-label="Notification actions"
>
<button
v-if="isRead"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsUnread')"
@click="markAsUnread"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t('markAsUnread') }}
{{ $t("markAsUnread") }}
</span>
</button>
<button v-if="!isRead"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsRead')"
@click="markAsRead"
>
<button
v-if="!isRead"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAsRead')"
@click="markAsRead"
>
<i class="fa fa-sm fa-envelope-open-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t('markAsRead') }}
{{ $t("markAsRead") }}
</span>
</button>
<a v-if="isButtonGroup"
<a
v-if="isButtonGroup"
type="button"
class="btn btn-outline-primary"
:href="showUrl"
:title="$t('action.show')"
>
>
<i class="fa fa-sm fa-comment-o"></i>
</a>
<!-- "Mark All Read" button -->
<button
v-if="showMarkAllButton"
class="btn"
:class="overrideClass"
type="button"
:title="$t('markAllRead')"
@click="markAllRead"
>
<i class="fa fa-sm fa-envelope-o"></i>
<span v-if="!buttonNoText" class="ps-2">
{{ $t("markAllRead") }}
</span>
</button>
</div>
</template>
<script>
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
export default {
name: "NotificationReadToggle",
@@ -57,7 +76,7 @@ export default {
// Optional
buttonClass: {
required: false,
type: String
type: String,
},
buttonNoText: {
required: false,
@@ -65,14 +84,14 @@ export default {
},
showUrl: {
required: false,
type: String
}
type: String,
},
},
emits: ['markRead', 'markUnread'],
emits: ["markRead", "markUnread"],
computed: {
/// [Option] override default button appearance (btn-misc)
overrideClass() {
return this.buttonClass ? this.buttonClass : 'btn-misc'
return this.buttonClass ? this.buttonClass : "btn-misc";
},
/// [Option] don't display text on button
buttonHideText() {
@@ -82,31 +101,48 @@ export default {
// When passed, the component return a button-group with 2 buttons.
isButtonGroup() {
return this.showUrl;
}
},
},
methods: {
markAsUnread() {
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/unread`, []).then(response => {
this.$emit('markRead', { notificationId: this.notificationId });
})
makeFetch(
"POST",
`/api/1.0/main/notification/${this.notificationId}/mark/unread`,
[]
).then((response) => {
this.$emit("markRead", {notificationId: this.notificationId});
});
},
markAsRead() {
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/read`, []).then(response => {
this.$emit('markUnread', { notificationId: this.notificationId });
})
makeFetch(
"POST",
`/api/1.0/main/notification/${this.notificationId}/mark/read`,
[]
).then((response) => {
this.$emit("markUnread", {
notificationId: this.notificationId,
});
});
},
markAllRead() {
makeFetch(
"POST",
`/api/1.0/main/notification/markallread`,
[]
).then((response) => {
this.$emit("markAllRead");
});
},
},
i18n: {
messages: {
fr: {
markAsUnread: 'Marquer comme non-lu',
markAsRead: 'Marquer comme lu'
}
}
}
}
markAsUnread: "Marquer comme non-lu",
markAsRead: "Marquer comme lu",
},
},
},
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -1,30 +1,33 @@
{% macro title(c) %}
<div class="item-row title">
<h2 class="notification-title">
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
<a
href="{{ chill_path_add_return_path('chill_main_notification_show', {
id: c.notification.id
}) }}"
>
{{ c.notification.title }}
</a>
</h2>
</div>
{% endmacro %}
{% macro header(c) %}
<div class="item-row notification-header mt-2">
<div class="item-col">
<ul class="small_in_title">
{% if c.step is not defined or c.step == 'inbox' %}
<li class="notification-from">
<span class="item-key">
<abbr title="{{ 'notification.received_from'|trans }}">
{{ 'notification.from'|trans }} :
</abbr>
</span>
<span class="item-key">
<abbr title="{{ 'notification.received_from' | trans }}">
{{ "notification.from" | trans }} :
</abbr>
</span>
{% if not c.notification.isSystem %}
<span class="badge-user">
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{{ c.notification.sender | chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{% else %}
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
<span class="badge-user system">{{ "notification.is_system" | trans }}</span>
{% endif %}
</li>
{% endif %}
@@ -32,34 +35,37 @@
<li class="notification-to">
{% if c.notification_cc is defined %}
{% if c.notification_cc %}
<span class="item-key">
<abbr title="{{ 'notification.sent_cc'|trans }}">
{{ 'notification.cc'|trans }} :
</abbr>
</span>
<span class="item-key">
<abbr title="{{ 'notification.sent_cc' | trans }}">
{{ "notification.cc" | trans }} :
</abbr>
</span>
{% else %}
<span class="item-key">
<abbr title="{{ 'notification.sent_to'|trans }}">
{{ 'notification.to'|trans }} :
</abbr>
</span>
<span class="item-key">
<abbr title="{{ 'notification.sent_to' | trans }}">
{{ "notification.to" | trans }} :
</abbr>
</span>
{% endif %}
{% else %}
<span class="item-key">
<abbr title="{{ 'notification.sent_to'|trans }}">
{{ 'notification.to'|trans }} :
</abbr>
</span>
<span class="item-key">
<abbr title="{{ 'notification.sent_to' | trans }}">
{{ "notification.to" | trans }} :
</abbr>
</span>
{% endif %}
{% for a in c.notification.addressees %}
<span class="badge-user">
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{{ a | chill_entity_render_string({'at_date': c.notification.date}) }}
</span>
{% endfor %}
{% for a in c.notification.addressesEmails %}
<span class="badge-user" title="{{ 'notification.Email with access link'|trans|e('html_attr') }}">
{{ a }}
</span>
<span
class="badge-user"
title="{{ 'notification.Email with access link'|trans|e('html_attr') }}"
>
{{ a }}
</span>
{% endfor %}
</li>
{% endif %}
@@ -70,7 +76,6 @@
</div>
</div>
{% endmacro %}
{% macro content(c) %}
<div class="item-row separator">
{% if c.data is defined %}
@@ -83,60 +88,77 @@
<div class="notification-content">
{% if c.full_content is defined and c.full_content == true %}
{% if c.notification.message is not empty %}
{{ c.notification.message|chill_markdown_to_html }}
{{ c.notification.message | chill_markdown_to_html }}
{% else %}
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
{% endif %}
{% else %}
{% if c.notification.message is not empty %}
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
<p class="read-more">
<a
href="{{ chill_path_add_return_path('chill_main_notification_show', {
id: c.notification.id
}) }}"
>{{ "Read more" | trans }}</a>
</p>
{% else %}
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
{% endif %}
{% endif %}
</div>
</div>
{% endmacro %}
{% macro actions(c) %}
{% if c.action_button is not defined or c.action_button != false %}
<div class="item-row separator">
<div class="item-col item-meta">
{% if c.notification.comments|length > 0 %}
<div class="comment-counter">
<span class="counter">
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
</span>
<span class="counter">
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
</span>
</div>
{% endif %}
</div>
<div class="item-col">
<ul class="record_actions">
<li>
{# Vue component #}
<span class="notification_toggle_read_status"
data-notification-id="{{ c.notification.id }}"
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
data-container="notification-status"
<span
class="notification_toggle_read_status"
data-notification-id="{{ c.notification.id }}"
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
data-container="notification-status"
></span>
</li>
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
<li>
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}"
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
<a
href="{{ chill_path_add_return_path(
'chill_main_notification_edit',
{ id: c.notification.id }
) }}"
class="btn btn-edit"
title="{{ 'Edit' | trans }}"
></a>
</li>
{% endif %}
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE',
c.notification) %}
<li>
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
<a
href="{{ chill_path_add_return_path(
'chill_main_notification_show',
{ id: c.notification.id }
) }}"
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}"
title="{{ 'notification.see_comments_thread' | trans }}"
>
{% if not c.notification.isSystem() %}
<i class="fa fa-comment"></i>
{% else %}
{{ 'Read more'|trans }}
{{ "Read more" | trans }}
{% endif %}
</a>
</li>
@@ -147,24 +169,30 @@
{% endif %}
{% endmacro %}
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
<div
class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}"
data-notification-id="{{ notification.id|escape('html_attr') }}"
>
{% if fold_item is defined and fold_item != false %}
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
<button type="button" class="accordion-button collapsed"
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
<button
type="button"
class="accordion-button collapsed"
data-bs-toggle="collapse"
data-bs-target="#flush-collapse-{{ notification.id }}"
aria-expanded="false"
aria-controls="flush-collapse-{{ notification.id }}"
>
{{ _self.title(_context) }}
</button>
{{ _self.header(_context) }}
</div>
<div id="flush-collapse-{{ notification.id }}"
<div
id="flush-collapse-{{ notification.id }}"
class="accordion-collapse collapse"
aria-labelledby="flush-heading-{{ notification.id }}"
data-bs-parent="#notification-fold">
data-bs-parent="#notification-fold"
>
{{ _self.content(_context) }}
</div>
{{ _self.actions(_context) }}
@@ -174,5 +202,4 @@
{{ _self.content(_context) }}
{{ _self.actions(_context) }}
{% endif %}
</div>

View File

@@ -1,62 +1,78 @@
{% extends "@ChillMain/layout.html.twig" %}
{% extends "@ChillMain/layout.html.twig" %}
{% block title 'notification.My own notifications'|trans %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
{{ parent() }}
{{ encore_entry_script_tags("mod_notification_toggle_read_status") }}
{{ encore_entry_script_tags("mod_notification_toggle_read_all_status") }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags("mod_notification_toggle_read_status") }}
{{ encore_entry_link_tags("mod_notification_toggle_read_all_status") }}
{% endblock %}
{% block content %}
<div class="col-10 notification notification-list">
<h1>{{ block('title') }}</h1>
<div class="col-10 notification notification-list">
<h1>{{ block("title") }}</h1>
<ul class="nav nav-pills justify-content-center">
<li class="nav-item">
<a
class="nav-link {% if step == 'inbox' %}active{% endif %}"
href="{{ path('chill_main_notification_my') }}"
>
{{ "notification.Notifications received" | trans }}
{% if unreads['inbox'] > 0 %}
<span class="badge rounded-pill bg-danger">
{{ unreads["inbox"] }}
</span>
{% endif %}
</a>
</li>
<li class="nav-item">
<a
class="nav-link {% if step == 'sent' %}active{% endif %}"
href="{{ path('chill_main_notification_sent') }}"
>
{{ "notification.Notifications sent" | trans }}
{% if unreads['sent'] > 0 %}
<span class="badge rounded-pill bg-danger">
{{ unreads["sent"] }}
</span>
{% endif %}
</a>
</li>
</ul>
<ul class="nav nav-pills justify-content-center">
<li class="nav-item">
<a class="nav-link {% if step == 'inbox' %}active{% endif %}" href="{{ path('chill_main_notification_my') }}">
{{ 'notification.Notifications received'|trans }}
{% if unreads['inbox'] > 0 %}
<span class="badge rounded-pill bg-danger">
{{ unreads['inbox'] }}
</span>
{% endif %}
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if step == 'sent' %}active{% endif %}" href="{{ path('chill_main_notification_sent') }}">
{{ 'notification.Notifications sent'|trans }}
{% if unreads['sent'] > 0 %}
<span class="badge rounded-pill bg-danger">
{{ unreads['sent'] }}
</span>
{% endif %}
</a>
</li>
</ul>
{% if datas|length == 0 %}
{% if step == 'inbox' %}
<p class="chill-no-data-statement">{{ 'notification.Any notification received'|trans }}</p>
{% if datas|length == 0 %} {% if step == 'inbox' %}
<p class="chill-no-data-statement">
{{ "notification.Any notification received" | trans }}
</p>
{% else %}
<p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p>
<p class="chill-no-data-statement">
{{ "notification.Any notification sent" | trans }}
</p>
{% endif %}
{% else %}
<div class="flex-table accordion accordion-flush" id="notification-fold">
{% for data in datas %}
{% set notification = data.notification %}
{% include '@ChillMain/Notification/_list_item.html.twig' with {
'fold_item': true,
'notification_cc': data.template_data.notificationCc is defined ? data.template_data.notificationCc : false
} %}
{% endfor %}
</div>
{% else %}
<div class="flex-table accordion accordion-flush" id="notification-fold">
{% for data in datas %}
{% set notification = data.notification %}
{% include '@ChillMain/Notification/_list_item.html.twig' with {
'fold_item': true, 'notification_cc': data.template_data.notificationCc
is defined ? data.template_data.notificationCc : false } %}
{% endfor %}
</div>
{{ chill_pagination(paginator) }}
{% endif %}
<ul class="record_actions sticky-form-buttons justify-content-end">
<li class="ml-auto d-flex align-items-center gap-2">
<span class="notification_all_read"></span>
</li>
</ul>
</div>
{{ chill_pagination(paginator) }}
{% endif %}
</div>
{% endblock content %}