/*
* Copyright 2024 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This tool displays the thread of an Ametys object
* @private
*/
Ext.define('Ametys.plugins.cms.threads.AmetysObjectThreadTool', {
extend: "Ametys.tool.SelectionTool",
statics:
{
/**
* Edit the thread
* @property {String} threadId The thread id
*/
editThread: function (threadId)
{
let tool = Ametys.tool.ToolsManager.getTool(Ametys.plugins.cms.threads.AmetysObjectThreadTool._toolId);
tool.serverCall (
'getComment',
[tool._objectId, threadId],
function(data)
{
let textArea = tool._timeline.down("#comment-input");
textArea.setValue(data.content);
textArea.focus();
let sendButton = tool._timeline.down("#comment-send-button");
sendButton.enable(); // Enable it before setting the tooltip
sendButton.setTooltip("{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_EDIT_COMMENT_BTN}}");
tool._timeline.addCls("disabled-timeline");
tool._editedCommentId = threadId;
let timelineItems = tool._timeline.getEl().select(".timeline-item").elements;
for (let item of timelineItems)
{
let extJsItem = Ext.get(item);
let table = extJsItem.up("table");
if (extJsItem.getAttribute("data-id") == threadId)
{
table.addCls("edited-timeline-item");
}
else
{
table.removeCls("edited-timeline-item");
}
}
},
{
errorMessage: true,
waitMessage: { target: tool.getWrapper() }
}
);
},
/**
* Delete the thread
* @property {String} threadId The thread id
*/
deleteThread: function (threadId)
{
let tool = Ametys.tool.ToolsManager.getTool(Ametys.plugins.cms.threads.AmetysObjectThreadTool._toolId);
Ametys.Msg.confirm(
"{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_DELETE_TITLE}}",
"{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_DELETE_MSG}}",
function(btn) {
if (btn == 'yes')
{
this.serverCall (
'deleteComment',
[this._objectId, threadId],
function(data)
{
this.showOutOfDate();
},
{
errorMessage: true,
waitMessage: true
}
);
}
},
tool
);
}
},
/** @cfg {String} triggerChar=@ The char that starts mentions */
triggerChar: '@',
/**
* @private
* @property {Ametys.timeline.Timeline} _timeline the timeline of comments
*/
/**
* @private
* @property {String} _objectId the id of the selected object
*/
_objectId: null,
/**
* @private
* @property {String} _editedCommentId the edited comment id
*/
_editedCommentId: null,
/**
* @private
* @property {Ext.Template} _contentHintTpl The template used for hint description
*/
_contentHintTpl: new Ext.Template("{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_CONTENT_HINT}}"),
/**
* @private
* @property {Ext.Template} _mentionedUserTpl The template used for mentiond users in comment content
*/
_mentionedUserTpl: new Ext.Template("<span class='mention {cls}' data-qtip='{userTooltip}'>{triggerChar}{username}</span>"),
/**
* @private
* A cache for user rights
*/
_userRightsCache: {},
constructor: function()
{
this.callParent(arguments);
Ametys.plugins.cms.threads.AmetysObjectThreadTool._toolId = this.id;
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onObjectDeleted, this);
},
createPanel: function()
{
var me = this;
this._timeline = Ext.create('Ametys.timeline.Timeline', {
scrollable: true,
dockedItems: [{
xtype: 'component',
dock: 'top',
ui: 'tool-hintmessage',
itemId: 'object-info',
html: ''
},
{
xtype: 'toolbar',
dock: 'top',
layout: {
type: 'hbox',
align: 'stretch'
},
items: [
{
// Comment input
xtype: 'ametystextarea.withmentions',
cls: 'ametys',
flex: 1,
itemId: 'comment-input',
emptyText: "{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_INPUT_EMPTY}}",
msgTarget: 'qtip',
triggerChar: this.triggerChar,
renderMentionedUserFn: Ext.bind(this._renderMentionedUser, this),
listeners: {
change: this._onFieldChange,
focus: this._onFieldFocus,
focusleave: this._onFieldFocusleave,
scope: this
}
},
{
xtype: 'container',
layout: 'vbox',
items: [
{
// Help
xtype: 'component',
html: '',
cls: 'ametys-description',
flex: 1,
listeners: {
afterrender: function(me)
{
// Register the new tip with an element's ID
Ext.tip.QuickTipManager.register({
target: me.getId(), // Target button's ID
text : "{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_INPUT_DESC}}" // Tip content
});
},
destroy: function(me)
{
Ext.tip.QuickTipManager.unregister(me.getId());
}
}
},
{
// Add button
xtype: 'button',
itemId: 'comment-send-button',
handler: Ext.bind (this._sendComment, me),
iconCls: 'a-btn-glyph ametysicon-desktop-paper-plane-light size-16',
cls: 'a-btn-light',
disabled: true,
flex: 1,
margin: '0 0 0 0'
}
]
}
]
}
],
timelineItemHTML: ['<div data-id="{id}" class="timeline-item {additionalCls}">',
'<div class="profile-img-wrap">',
'<img src="{profileImg}" alt="">',
'<div>{hour}</div>',
'</div>',
'<div class="contents-wrap">',
'<span class="vertical-line"></span>',
'<div class="text">',
'<div class="header">',
'<strong class="title" data-qtip="{userTooltip}">{username}</strong></br>',
'<div class="header-buttons">',
'<tpl if="canEdit">',
'<span data-qtip="{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_EDIT_TOOLTIP}}" onclick="Ametys.plugins.cms.threads.AmetysObjectThreadTool.editThread(\'{id}\')" class="thread-button a-btn-glyph ametysicon-edit45"></span>',
'</tpl>',
'<tpl if="canDelete">',
'<span data-qtip="{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_DELETE_TOOLTIP}}" onclick="Ametys.plugins.cms.threads.AmetysObjectThreadTool.deleteThread(\'{id}\')" class="thread-button a-btn-glyph ametysicon-sign-raw-cross"></span>',
'</tpl>',
'</div>',
'</div>',
'<div class="content">',
'{text}',
'<tpl if="lastModified">',
'<span class="thread-modified" data-qtip="{lastModified:date(Ext.Date.patterns.FriendlyDateTime)}">{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_EDIT_MSG}}</span>',
'</tpl>',
'</div>',
'</div>',
'</div>',
'</div>'],
});
return Ext.create('Ext.panel.Panel', {
cls: 'ao-thread-tool',
scrollable: false,
border: false,
layout: 'card',
activeItem: 0,
items: [{
xtype: 'component',
cls: 'a-panel-text-empty',
border: false,
html: ''
},
this._timeline
]
});
},
_renderMentionedUser: function(mention, displayField, prefixChar)
{
let highlightCls = "current-user";
let isCurrentUser = this._isCurrentUser(mention.data);
if (!isCurrentUser)
{
highlightCls = "";
let userId = mention.id;
if (this._userRightsCache[userId] == null)
{
this._checkRight(userId);
}
else if (!this._userRightsCache[userId])
{
highlightCls = "no-right";
}
}
return "<strong data-display-user=\"" + mention.id + "\" + class=\"" + highlightCls + "\"><span>" + prefixChar + mention.get(displayField) + "</span></strong>";
},
_checkRight: function(userId)
{
let me = this;
this.serverCall (
'hasAccessRight',
[userId, this._objectId],
function(hasRight)
{
if (!hasRight)
{
Ext.select("[data-display-user=\"" + userId + "\"]").addCls("no-right");
}
me._userRightsCache[userId] = hasRight;
},
{
waitMessage: false,
errorMessage: false
}
);
},
refresh: function ()
{
this.showRefreshing();
this._userRightsCache = {};
var objectTarget = this.getCurrentSelectionTargets()[0];
this._objectId = objectTarget.getParameters().id;
this._timeline.getComponent("object-info").update(this._contentHintTpl.applyTemplate([Ext.String.escapeHtml(objectTarget.getParameters().title)]));
var comment = this._timeline.queryById("comment-input");
comment.reset();
this._clearEdition();
let sendButton = this._timeline.down("#comment-send-button");
sendButton.setTooltip("{{i18n PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_SEND_COMMENT_BTN}}");
this.serverCall ('getThread', [this._objectId], this._getCommentsCb, { errorMessage: true });
},
/**
* Clear the filter search
* @param {Ext.Button} btn The button
* @private
*/
_sendComment: function()
{
var comment = this._timeline.queryById("comment-input");
var commentValue = this._escapeCommentText(comment.getValue());
if (this._editedCommentId)
{
this.serverCall ('editComment', [this._objectId, this._editedCommentId, commentValue], this._editCommentCb, { errorMessage: true, refreshing: true });
}
else
{
this.serverCall ('addComment', [this._objectId, commentValue], this._addCommentCb, { errorMessage: true, refreshing: true });
}
},
_addCommentCb: function()
{
var comment = this._timeline.queryById("comment-input");
comment.reset();
this.showOutOfDate();
},
_editCommentCb: function()
{
this._clearEdition();
var comment = this._timeline.queryById("comment-input");
comment.reset();
this.showOutOfDate();
},
_clearEdition: function()
{
this._editedCommentId = null;
this._timeline.removeCls("disabled-timeline");
let timelineItems = this._timeline.getEl().select(".timeline-item").elements;
for (let item of timelineItems)
{
let extJsItem = Ext.get(item);
let table = extJsItem.up("table");
table.removeCls("edited-timeline-item");
}
},
/**
* @private
* Callback function called after retrieving the 'alert' state of content targets
* @param params The JSON result
*/
_getCommentsCb: function (comments)
{
var data = [];
for (var i = 0; i < comments.length; i++)
{
var d = this._convertComment2Timeline(comments[i]);
data.push(d);
}
this._timeline.getStore().loadData(data);
this.getContentPanel().getLayout().setActiveItem(1);
this.showRefreshed();
},
_convertComment2Timeline: function (comment)
{
var additionalCls;
if (this._isCurrentUser(comment.author))
{
additionalCls = 'current-user';
}
var username = comment.author.fullname || "{{i18n plugin.core:PLUGINS_CORE_USERS_UNKNOWN_USER}}";
var userTooltip = this._getUserTooltip(comment.author, false);
var text = comment.mentions ? this._convertCommentContentMentions(comment) : comment.content;
return {
date: comment["creation-date"],
additionalCls: additionalCls,
username: username,
userTooltip: userTooltip,
profileImg: Ametys.helper.Users.getUserImage(comment.author.login, comment.author.populationId, 46),
text: text,
canEdit: comment.canEdit,
canDelete: comment.canDelete,
lastModified: comment["modification-date"],
id: comment.id
}
},
_convertCommentContentMentions: function (comment)
{
var text = comment.content;
for (var i = 0; i < comment.mentions.length; i++)
{
var mentionedUser = comment.mentions[i];
var username = mentionedUser.fullname ? mentionedUser.fullname : "{{i18n plugin.core:PLUGINS_CORE_USERS_UNKNOWN_USER}}";
var userTooltip = this._getUserTooltip(mentionedUser, true);
var cls = this._getMentionedUserCls(mentionedUser);
var replaceMentionBy = this._mentionedUserTpl.applyTemplate( {
userTooltip: userTooltip,
username: username,
cls: cls,
triggerChar: this.triggerChar
});
var login = mentionedUser.login;
var populationId = mentionedUser.populationId;
var mentionValue = "@(" + login + "#" + populationId + ")";
text = text.replaceAll(mentionValue, replaceMentionBy);
}
return text;
},
_getMentionedUserCls: function(user)
{
if (this._isCurrentUser(user))
{
return 'current-user'
}
else if (!user.hasRight)
{
return 'no-right'
}
return null;
},
_isCurrentUser: function (user)
{
var currentUser = Ametys.getAppParameter('user');
return currentUser.login === user.login && currentUser.population === user.populationId;
},
_getUserTooltip: function(user, checkRight)
{
var login = user.login;
var populationId = user.populationId;
var username = user.fullname || "{{i18n plugin.core:PLUGINS_CORE_USERS_UNKNOWN_USER}}";
var populationLabel = user.populationLabel || populationId;
var renderUser = Ametys.helper.Users.renderUser(login, populationLabel, username);
return !checkRight || user.hasRight
? renderUser
: renderUser + "<br/><i>{{i18n plugin.cms:PLUGINS_CMS_UITOOL_AMETYS_OBJECT_THREAD_NO_RIGHT_TOOLTIP}}</strong></i>"
},
_onFieldChange: function(field, newValue)
{
var sendCommentButton = this._timeline.queryById("comment-send-button");
var disable = Ext.isEmpty(newValue);
sendCommentButton.setDisabled(disable);
},
_onFieldFocus: function(field)
{
field.addCls("a-field-focus");
},
_onFieldFocusleave: function(field)
{
field.removeCls("a-field-focus");
},
_escapeCommentText: function(text)
{
return text.replace(/&/g, '&').replace(/</g, '<').replace(/\n/g, '<br/>');
},
setNoSelectionMatchState: function (message)
{
this.callParent(arguments);
var panel = this.getContentPanel().items.get(0);
panel.update(message);
this.getContentPanel().getLayout().setActiveItem(0);
this._objectId = null;
},
_onObjectDeleted: function (message)
{
if (this.getTargetsInCurrentSelectionTargets(message).length > 0)
{
this.setNoSelectionMatchState();
}
}
});