import { BnumMessage, eMessageType } from '../../../mel_metapage/js/lib/classes/bnum_message.js';
import { EMPTY_STRING } from '../../../mel_metapage/js/lib/constants/constants.js';
import { MelObject } from '../../../mel_metapage/js/lib/mel_object.js';
import { PostComment, PostCommentView } from './comments.js';
export class Manager extends MelObject {
constructor() {
super();
this.post_uid = rcmail.env.post_uid; // Initialisation de `post_uid` depuis l'environnement
this.post_id = rcmail.env.post_id; // Initialisation de `post_id` depuis l'environnement
// this.show_comments = rcmail.env.show_comments; // Initialisation de `show_comments`
this.sort_order = 'date_asc'; // Valeur par défaut ou à définir dynamiquement
this.parent_comment_id = null; // Parent comment ID (optionnel)
}
/**
* Initialise la logique de la page des commentaires, y compris le chargement des commentaires,
* la gestion de l'ordre de tri, et l'interaction avec les éléments de l'interface utilisateur.
*
* Cette fonction effectue les opérations suivantes :
* - Charge l'ordre de tri des commentaires depuis le LocalStorage ou utilise 'date_asc' par défaut.
* - Assigne la valeur d'ordre de tri au select pour afficher la sélection correcte.
* - Affiche les commentaires en fonction de l'ordre de tri récupéré.
* - Gère le changement de tri des commentaires via un événement de sélection.
* - Permet de rediriger l'utilisateur vers la page d'accueil au clic sur un bouton dédié.
* - Configure le redimensionnement automatique du textarea pour les nouveaux commentaires.
* - Affiche ou masque les boutons de commentaire selon l'état du textarea.
*
* @returns {void} - Cette fonction n'a pas de valeur de retour.
*/
main() {
super.main();
// Gestion du bouton d'action du post
this.initButtons();
// Charger l'ordre de tri depuis le LocalStorage, sinon utiliser 'date_desc' par défaut
const savedSortOrder =
localStorage.getItem('commentSortOrder') || 'date_asc';
// Assigner la valeur sauvegardée au select pour afficher la sélection correcte
$('#forum-comment-select').val(savedSortOrder);
// Afficher les commentaires avec l'ordre de tri récupéré (ou par défaut si aucun tri sauvegardé)
Manager.displayComments(savedSortOrder);
// Associer l'événement de changement de tri au select
$('#forum-comment-select').change(async (event) => {
const selectedValue = $(event.target).val(); // Récupérer la valeur sélectionnée
// Sauvegarder la sélection dans le LocalStorage
localStorage.setItem('commentSortOrder', selectedValue);
// Appeler displayComments avec l'ordre sélectionné
await Manager.displayComments(selectedValue);
});
// Exporter 'manager'
this.export('manager');
// Redirection à la page d'accueil au clic sur 'return-homepage'
$('#return-homepage').click(() => {
$("body").css("cursor", "wait");
window.location.href = this.url('forum', {
action: 'index',
params: { _workspace_uid: this.get_env('workspace_uid') },
});
});
$('#return-homepage').on("keydown", (event) => {
if (event.keyCode === 13) { // Touche "Entrée"
$('#return-homepage').click();
}
});
// Fonction de redimensionnement automatique du textarea
$(document).on('input', '.forum-comment-input', function () {
this.style.height = 'auto'; // Réinitialise la hauteur
this.style.height = this.scrollHeight + 'px'; // Ajuste la hauteur
});
// Configuration de la visibilité des boutons
const $textarea = $('#new-comment-textarea');
const $buttonsContainer = $('#buttons-container');
const $cancelButton = $('#cancel-comment');
// Initialement masqué
$buttonsContainer.addClass('hidden');
// Afficher les boutons lorsque le textarea reçoit le focus
$textarea.on('focus', function () {
$buttonsContainer.removeClass('hidden');
});
// Gestion du bouton "Annuler"
$cancelButton.on('click', function () {
$textarea.val(EMPTY_STRING); // Réinitialiser le contenu du textarea
$textarea.height('auto'); // Revenir à la taille d'origine
$buttonsContainer.addClass('hidden'); // Cacher les boutons "Annuler" et "Sauvegarder"
});
// Configuration du bouton "Sauvegarder"
const $saveButton = $('#submit-comment');
$saveButton.click(() => {
const commentContent = $textarea.val();
if (commentContent.trim() !== EMPTY_STRING) {
// Appeler la fonction de sauvegarde
this.saveComment(commentContent);
}
});
}
/**
* Enregistre un nouveau commentaire et met à jour l'affichage.
*
* Cette fonction envoie le contenu du commentaire à l'API pour le créer,
* puis réinitialise le champ de texte et affiche le nouveau commentaire en haut
* de la liste en cas de succès. En cas d'erreur, un message d'erreur est affiché.
*
* @async
* @function saveComment
* @param {string} content - Le contenu du commentaire à enregistrer.
* @returns {void}
* @throws {Error} En cas d'échec de l'enregistrement ou d'une erreur réseau.
*/
async saveComment(content) {
// Désactiver le bouton de validation pour éviter les clics multiples
const submitButton = $('#submit-comment');
submitButton.prop('disabled', true);
try {
const id = rcmail.display_message('loading', 'loading');
const response = await this.http_internal_post({
task: 'forum',
action: 'create_comment',
params: {
_post_id: this.post_id,
_content: content,
},
});
rcmail.hide_message(id);
if (response.status === 'success') {
rcmail.display_message(response.message, 'confirmation');
$('#new-comment-textarea').val(EMPTY_STRING); // Réinitialiser le textarea
// Afficher le nouveau commentaire dans l'interface sans recharger la page
await Manager.displaySingleComment(response.comment);
} else {
rcmail.display_message(response.message, 'error');
}
} catch (error) {
rcmail.display_message(
rcmail.gettext('mel_forum.comment_save_error'),
'error',
);
console.error(rcmail.gettext('mel_forum.comment_save_failure'), error);
} finally {
// Réactiver le bouton de validation une fois la requête terminée
submitButton.prop('disabled', false);
}
}
/**
* Affiche les commentaires associés à un post spécifique dans l'interface utilisateur.
*
* Cette méthode récupère tous les commentaires liés à un post en utilisant une instance de `PostCommentView`.
* Les commentaires sont ensuite instanciés en tant qu'objets `PostComment`, leur contenu HTML est généré et ajouté
* dynamiquement au DOM. Des événements de clic pour les boutons "like" et "dislike" sont également attachés après
* l'affichage des commentaires.
*
* @async
* @function
* @name displayComments
* @param {string} [order='date_desc'] - L'ordre dans lequel afficher les commentaires (par défaut 'date_desc').
* @param {string|null} [parent_comment_id=null] - L'identifiant du commentaire parent pour filtrer les réponses (s'il y a lieu).
* @returns {Promise<void>} Retourne une promesse qui est résolue une fois que tous les commentaires sont affichés et que les événements sont attachés.
*/
static async displayComments(order = 'date_desc', parent_comment_id = null) {
BnumMessage.SetBusyLoading();
let PostCommentManager = new PostCommentView(
rcmail.env.post_uid,
rcmail.env.post_id,
order,
parent_comment_id,
);
// Passer l'option de tri choisie à la fonction getCommentByPost
let allComments;
try {
allComments = await PostCommentManager.getCommentByPost();
} catch (error) {
console.error(rcmail.gettext('mel_forum.comments_fetch_error'), error);
// Vous pouvez afficher un message d'erreur à l'utilisateur ici si nécessaire
return;
}
let comments_array = [];
let responses_array = [];
// Ajouter chaque commentaire à un tableau pour traitement
for (const key in allComments) {
if (allComments && allComments[key]) {
const comment = allComments[key];
let commentVizualizer = new PostComment(
comment.id,
comment.uid,
comment.post_id,
comment.user_email,
comment.user_name,
comment.content,
comment.created,
comment.likes,
comment.dislikes,
comment.parent,
comment.children_number,
comment.current_user_reacted,
);
// Si le commentaire est un commentaire principal (sans parent)
if (!comment.parent) {
comments_array.push(commentVizualizer);
} else {
responses_array.push(commentVizualizer);
}
}
}
//Si on affiche que les commentaires principaux
if (!parent_comment_id) {
// Vider la zone des commentaires avant de ré-afficher les commentaires récupérés
$('#comment-area').empty();
for (const comment of comments_array) {
let commentHtml = comment.generateHtmlFromTemplate();
// Ajouter le commentaire principal à la zone des commentaires
$('#comment-area').append(...commentHtml);
}
} else if (responses_array.length) {
for (const response of responses_array) {
let responseHtml = response.generateHtmlFromTemplate();
$(`#responses-${parent_comment_id}`).removeClass('hidden');
$(`#responses-${parent_comment_id}`).append(...responseHtml);
}
}
BnumMessage.StopBusyLoading();
}
/**
* Affiche un commentaire dans la section appropriée de l'interface utilisateur.
*
* @param {Object} comment - L'objet commentaire à afficher.
* @param {number} comment.id - L'identifiant unique du commentaire.
* @param {string} comment.uid - L'identifiant unique de l'utilisateur ayant posté le commentaire.
* @param {number} comment.post_id - L'identifiant du post auquel le commentaire appartient.
* @param {number} comment.user_email - L'email de l'utilisateur ayant posté le commentaire.
* @param {string} comment.user_name - Le nom de l'utilisateur ayant posté le commentaire.
* @param {string} comment.content - Le contenu du commentaire.
* @param {string} comment.created - La date de création du commentaire au format 'YYYY-MM-DD HH:mm:ss'.
* @param {number} [comment.likes=0] - Le nombre de likes sur le commentaire (0 par défaut).
* @param {number} [comment.dislikes=0] - Le nombre de dislikes sur le commentaire (0 par défaut).
* @param {number} [comment.parent=null] - L'identifiant du commentaire parent si le commentaire est une réponse, sinon `null`.
* @param {number} [comment.children_number=0] - Le nombre de réponses à ce commentaire (0 par défaut).
* @param {boolean} [comment.current_user_reacted=false] - Indique si l'utilisateur actuel a réagi au commentaire (false par défaut).
*
* Crée une instance de `PostComment` pour le commentaire fourni, génère le HTML associé et l'ajoute à la section appropriée :
* - Si le commentaire n'a pas de parent, il est ajouté directement à la zone de commentaires principale.
* - Si le commentaire est une réponse, il est ajouté au conteneur des réponses du commentaire parent. Si ce conteneur n'existe pas encore, il est créé.
*
*/
static async displaySingleComment(comment) {
// Formater la date et l'heure du commentaire avant de passer les données à PostComment
const formattedDate = new Date(comment.created).toLocaleDateString(
'fr-FR',
{
year: 'numeric',
month: 'long',
day: 'numeric',
},
);
const formattedTime = new Date(comment.created).toLocaleTimeString(
'fr-FR',
{
hour: '2-digit',
minute: '2-digit',
},
);
// Créer l'objet PostComment avec les données du commentaire
let commentVizualizer = new PostComment(
comment.id,
comment.uid,
comment.post_id,
comment.user_email,
comment.user_name,
comment.content,
`${formattedDate} à ${formattedTime}`,
comment.likes,
comment.dislikes,
comment.parent,
comment.children_number,
comment.current_user_reacted,
);
// Générer le HTML pour le commentaire
let commentHtml = $(commentVizualizer.generateHtmlFromTemplate());
// Si le commentaire est un commentaire principal (sans parent)
if (!comment.parent) {
// Avant de prépendre, vérifie si le commentaire est déjà dans le DOM
if (!$('#comment-area').find(`#comment-${comment.id}`).length) {
// Ajouter le commentaire en haut de la liste des commentaires principaux
$('#comment-area').prepend(commentHtml);
}
} else {
// Si c'est une réponse à un commentaire parent
let parent_comment_id = comment.parent;
// Assurer que la zone des réponses est visible
$(`#responses-${parent_comment_id}`).removeClass('hidden');
// Avant de prépendre, vérifie si la réponse est déjà dans le DOM
if (
!$(`#responses-${parent_comment_id}`).find(`#comment-${comment.id}`)
.length
) {
// Ajouter la réponse en haut des réponses du commentaire parent
$(`#responses-${parent_comment_id}`).prepend(commentHtml);
}
}
}
/**
* Initialise les actions des boutons de la page
*/
initButtons() {
const more_action = $('#more-action');
const context_menu = $('#post-context-menu');
const button_copy = $('#copy-post');
const button_edit = $('#edit-post');
const button_delete = $('#delete-post');
const button_add_like = $('#add_like');
const button_add_dislike = $('#add_dislike');
//Ne pas afficher les boutons d'édition et supression si l'utilisateur n'a pas les droits suffisant
if(!rcmail.env.has_owner_rights){
button_delete.toggleClass('hidden');
button_edit.toggleClass('hidden');
}
more_action.click(()=>
{
context_menu.toggleClass('hidden');
// Si le menu est visible, ajouter un écouteur pour détecter les clics extérieurs
if (!context_menu.hasClass('hidden')) {
// Ajouter un écouteur de clic sur tout le document après un léger délai
setTimeout(() => {
$(document).on('click.menuOutside', function(event) {
// Vérifier si le clic est en dehors du menu et du bouton trigger
if (!$(event.target).closest(context_menu).length && !$(event.target).closest(more_action).length) {
context_menu.addClass('hidden'); // Masquer le menu
$(document).off('click.menuOutside'); // Retirer l'écouteur après fermeture
}
});
// Ajouter un écouteur d'événements pour chaque bouton du menu
context_menu.find('.post-options-button').on('click', function() {
context_menu.addClass('hidden'); // Fermer le menu
$(document).off('click.menuOutside'); // Retirer l'écouteur après fermeture
});
}, 0); // Délai de 0 pour que l'événement de clic sur le bouton soit géré en premier
} else {
// Si le menu est caché, retirer l'écouteur du document
$(document).off('click.menuOutside');
}
});
//Gestion du clavier
more_action.on("keydown", (event) => {
if(event.keyCode === 13) {
more_action.click();
}
});
button_copy.click(() =>{
this.copyPostLink();
});
button_edit.click(() =>{
this.editPost();
});
button_delete.click(() =>{
this.deletePost();
});
// Initialisation de l'affichage boutons like et dislike en fonction de l'utilisateur.
if (rcmail.env.has_liked) {
button_add_like.addClass('filled');
}
if (rcmail.env.has_disliked) {
button_add_dislike.addClass('filled');
}
button_add_like.click(()=> {
this.addLikeOrDislike('like');
});
button_add_dislike.click(()=> {
this.addLikeOrDislike('dislike');
});
//gestion du scroll sur la page
document.querySelector('.content').addEventListener("scroll", () => {
const scrollPos = document.querySelector('.content').scrollTop;
if(scrollPos === 0){
$('#backToTop').addClass('hidden');
} else {
$('#backToTop').removeClass('hidden');
}
});
//action backToTop
$('#backToTop').click(() => {
document.querySelector('.content').scrollTo({
top:0,
behavior:"smooth",
});
});
}
/**
* copie dans le presse papier l'url de la page
*/
copyPostLink(){
let url = window.location.href.replaceAll("&_is_from=iframe", "&_force_bnum=1");
navigator.clipboard.writeText(url).then(() => {
BnumMessage.DisplayMessage(
rcmail.gettext('mel_forum.link_copied'),
eMessageType.Confirmation,
);
});
}
/**
* Permet d'éditer un post.
*
* - Construit l'URL d'édition en utilisant l'uid du post et du workspace.
* @returns {void}
*/
editPost() {
// Rediriger vers la page d'édition avec l'UID du post
window.location.href = this.url('forum', { action: 'create_or_edit_post', params:{'_uid': rcmail.env.post_uid, '_workspace_uid': rcmail.env.workspace_uid}});
}
/**
* Supprime un post spécifique après confirmation de l'utilisateur.
*
* - Empêche les comportements par défaut et la propagation de l'événement.
* - Affiche une boîte de dialogue de confirmation avant de procéder.
* - Envoie une requête HTTP pour supprimer le post.
* - Gère les retours de succès ou d'erreur et met à jour l'affichage en conséquence.
* @returns {void}
*/
deletePost() {
// Demander confirmation à l'utilisateur avant de supprimer
const confirmation = confirm(rcmail.gettext('mel_forum.delete_post_confirm'));
if (!confirmation) return; // Arrêter la fonction si l'utilisateur annule
// Envoi d'une requête HTTP pour supprimer le post
this.http_internal_post({
task: 'forum',
action: 'delete_post',
params: {
_uid: rcmail.env.post_uid,
},
processData: false,
contentType: false,
on_success: (response) => {
const parsedResponse = JSON.parse(response);
if (parsedResponse.status === 'success') {
// Affichage du message de succès
BnumMessage.DisplayMessage(
parsedResponse.message || rcmail.gettext('mel_forum.delete_post_success'),
eMessageType.Confirmation
);
//retourner à la page d'accueil
window.location.href = this.url('forum', {
action: 'index',
params: { _workspace_uid: this.get_env('workspace_uid') },
});
} else {
// Affichage du message d'erreur en cas d'échec
BnumMessage.DisplayMessage(
parsedResponse.message || rcmail.gettext('mel_forum.delete_post_failure'),
eMessageType.Error
);
}
},
on_error: (err) => {
// Affichage du message d'erreur en cas de problème avec la requête
BnumMessage.DisplayMessage(
rcmail.gettext('mel_forum.delete_post_failure'),
eMessageType.Error
);
}
});
}
/**
* Gestion des likes et dislike des posts
* @param {*} type type de la reaction
*/
addLikeOrDislike(type){
this.http_internal_post({
task: 'forum',
action: 'manage_reaction',
params: {
_post_id: rcmail.env.post_id,
_type: type,
},
processData:false,
contentType:false,
on_success: () => {
let like_div = $('#add_like');
let like_counter = like_div.find('span.ml-2');
let dislike_div = $('#add_dislike');
let dislike_counter = dislike_div.find('span.ml-2');
if (type ==='like'){
if (like_div.hasClass('filled')){
like_div.removeClass('filled');
this.updateCounter(like_counter, -1);
}else{
like_div.addClass('filled');
this.updateCounter(like_counter, 1);
if(dislike_div.hasClass('filled')) {
dislike_div.removeClass('filled');
this.updateCounter(dislike_counter, -1);
}
}
} else if (type === 'dislike'){
if (dislike_div.hasClass('filled')){
dislike_div.removeClass('filled');
this.updateCounter(dislike_counter, -1);
}else{
dislike_div.addClass('filled');
this.updateCounter(dislike_counter, 1);
if(like_div.hasClass('filled')){
like_div.removeClass('filled');
this.updateCounter(like_counter, -1);
}
}
}
},
on_error: (err) => {
BnumMessage.DisplayMessage(
rcmail.gettext('mel_forum.error_editing'),
eMessageType.Error,
);
},
});
}
/**
* Met à jour le compteur de like , si on est à 0 n'affiche rien
* @param {*} span élément html à mettre à jour
* @param {*} value modification apportée au compteur
*/
updateCounter(span, value) {
let currentValue = parseInt(span.text()) || 0; // Récupérer la valeur actuelle
let newValue = currentValue + value;
span.text(newValue);
}
}