tine20/Calendar/js/iMIPDetailsPanel.js
Rama main · réplica de lectura del repositorio original.
/*
* Tine 2.0
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss <c.weiss@metaways.de>
* @copyright Copyright (c) 2011-2012 Metaways Infosystems GmbH (http://www.metaways.de)
*/
Ext.ns('Tine.Calendar');
/**
* display panel for MIME type text/calendar
*
* NOTE: this panel is registered on Tine.Calendar::init
*
* @namespace Tine.Calendar
* @class Tine.Calendar.iMIPDetailsPanel
* @author Cornelius Weiss <c.weiss@metaways.de>
* @constructor
*/
Tine.Calendar.iMIPDetailsPanel = Ext.extend(Tine.Calendar.EventDetailsPanel, {
/**
* @cfg {Object} preparedPart
* server prepared text/calendar iMIP part
*/
preparedPart: null,
/**
* @property actionToolbar
* @type Ext.Toolbar
*/
actionToolbar: null,
/**
* @property iMIPrecord
* @type Tine.Calendar.Model.iMIP
*/
iMIPrecord: null,
allowViewInCalendar: true,
/**
* init this component
*/
initComponent: function() {
this.app = Tine.Tinebase.appMgr.get('Calendar');
this.resolveIMIP(this.messageRecord.get('preparedParts')[0].preparedData);
this.initIMIPToolbar();
this.on('afterrender', async (cmp, ownerCt) => {
const attendeeList = this.getAttendeeList();
await this.attendeeCombo.syncStore(attendeeList);
this.showIMIP();
});
this.on('updateEvent', async (result) => {
this.resolveIMIP(result);
this.showIMIP();
});
Tine.Calendar.iMIPDetailsPanel.superclass.initComponent.call(this);
},
resolveIMIP(data) {
if (data) {
this.prepareData = data;
this.iMIPrecord = new Tine.Calendar.Model.iMIP(this.prepareData);
if (!this.iMIPrecord.get('event')) {
this.iMIPrecord.set('event', this.prepareData.event);
}
}
this.hasExistingEvent = !!this.iMIPrecord.get('existing_event');
const preconditions = this.iMIPrecord.get('preconditions');
const eventData = (this.hasExistingEvent && !preconditions) ? this.iMIPrecord.get('existing_event') : this.iMIPrecord.get('event');
this.record = Tine.Calendar.backend.recordReader({
responseText: Ext.util.JSON.encode(eventData)
});
// show container from existing event if exists
const containerId = this.iMIPrecord.get('existing_event')?.container_id;
if (this.hasExistingEvent && containerId) {
this.record.set('container_id', containerId);
}
},
/**
* process IMIP
*
* @param {String} status
*/
processIMIP: async function (status, range) {
if (this.record.isRecurBase() && status !== 'ACCEPTED' && !range) {
Tine.widgets.dialog.MultiOptionsDialog.openWindow({
title: this.app.i18n._('Reply to Recurring Event'),
questionText: this.app.i18n._('You are responding to a recurring event. What would you like to do?'),
height: 170,
scope: this,
options: [
{text: this.app.i18n._('Respond to the whole series'), name: 'series'},
{text: this.app.i18n._('Do not respond'), name: 'cancel'}
],
handler: function (option) {
if (option !== 'cancel') {
this.processIMIP(status, option);
}
}
});
return;
}
if (!this.targetAttendeeRecord) return;
const attendeeContainers = this.iMIPrecord.get('attendeeContainersAvailable');
const userId = this.targetAttendeeRecord.data.user_id?.id ?? this.targetAttendeeRecord.data.user_id;
const options = Object.entries(attendeeContainers)
.filter(([key]) => key.includes(userId))
.flatMap(([, records]) => records.map(record => ({ text: record.name, name: record })));
if (options.length === 0) return;
const processUpdate = (selectedOption) => {
const targetAttendeeData = {...this.targetAttendeeRecord.data};
if (selectedOption) {
targetAttendeeData.displaycontainer_id = selectedOption;
}
targetAttendeeData.status = status;
this.getLoadMask().show();
Tine.Calendar.iMIPProcess(this.iMIPrecord.data, targetAttendeeData, (result, response) => {
if (!response.error) {
this.targetAttendeeRecord.set('status', status);
this.targetAttendeeRecord.set('displaycontainer_id', selectedOption);
}
this.fireEvent('updateEvent', result);
}, this);
};
if (this.targetAttendeeRecord.get('status') !== 'NEEDS-ACTION' || options.length === 1) {
return processUpdate();
}
Tine.widgets.dialog.MultiOptionsDialog.openWindow({
title: this.app.i18n._('Update Status'),
questionText: this.app.i18n._('Please select a calendar before update your status'),
height: 170,
allowCancel: true,
scope: this,
options: options,
handler: function(option) {
if (option !== 'cancel') {
processUpdate(option);
} else {
this.updateInfo();
}
}
});
},
/**
* iMIP action toolbar
*/
initIMIPToolbar: function() {
this.viewInCalendarAction = new Ext.Button(new Ext.Action({
handler: () => {
Tine.Calendar.ViewInCalendarDialog.openWindow({
record: this.record,
messageRecord: this.messageRecord,
targetAttendeeRecord: this.targetAttendeeRecord,
allowViewInCalendar: false,
});
},
icon: 'images/icon-set/icon_calendar.svg',
tooltip: this.app.i18n._('View and Edit Event In Calendar'),
hidden: !this.allowViewInCalendar,
style: "margin: 0 10px;",
}));
// Create the static text display with hover functionality
this.attendeeTextContainer = new Ext.Container({
hidden: true,
cls: 'attendee-text-container',
listeners: {
scope: this,
render: function(c) {
c.getEl().on({
scope: this,
click: ()=> {
this.activateCombo();
},
});
}
},
items: [
this.attendeeLabel = new Ext.Component({
autoEl: {
tag: 'div',
cls: 'xtb-text',
html: 'Select Attendee'
}
}),
]
});
// Create the combo (initially hidden)
this.attendeeCombo = new Tine.Calendar.AttendeeCombo({
eventRecord: this.record,
messageRecord: this.messageRecord,
defaultValue: 'current',
width: 300,
listeners: {
scope: this,
select: (combo, record)=> {
this.updateInfo();
},
}
});
// Create the static text display with hover functionality
this.attendeeComboContainer = new Ext.Container({
hidden: true,
cls: 'felamimail-action-invitation',
items: [this.attendeeCombo]
});
// Create a container that will hold both the text and combo
this.idPrefix = Ext.id();
this.statusActions = [];
this.statuses = Tine.Tinebase.widgets.keyfield.StoreMgr.get('Calendar', 'attendeeStatus');
this.statuses.each((status) => {
this.statusActions.push(new Ext.Button(new Ext.Action({
id: this.idPrefix + '-tglbtn-' + status.id,
text: status.id === 'NEEDS-ACTION' ? this.app.i18n._('Save') : status.get('i18nValue'),
xtype: 'tbbtnlockedtoggle',
enableToggle: true,
handler: this.processIMIP.createDelegate(this, [status.id]),
icon: status.get('icon'),
hidden: true,
toggleGroup: 'attendee-status-tglgroup',
cls: 'felamimail-action-invitation',
tooltip: status.id === 'NEEDS-ACTION' ? this.app.i18n._('Save to calendar without responding') : status.get('i18nValue'),
})));
});
// add more actions here (no spam / apply / crush / send event / ...)
this.iMIPclause = new Ext.Toolbar.TextItem({
text: '',
style: "margin: 5px 10px;",
});
this.descriptionField = new Ext.form.VueAlert({
variant: 'warning',
hidden: true,
});
this.tbar = this.actionToolbar = new Ext.Panel({
layout: 'fit',
border: false,
items: [
this.descriptionField,
{
xtype: 'container',
cls: 'cal-event-response-panel',
items: [
this.iMIPclause,
{
xtype: 'container',
cls: 'cal-event-response-panel-group-right',
items: [
{
xtype: 'container',
cls: 'subgroup',
items: [
this.attendeeTextContainer,
this.attendeeComboContainer,
this.viewInCalendarAction
],
},
{
xtype: 'container',
cls: 'subgroup',
items: this.statusActions,
}
]
}
]
},
]
});
},
activateCombo() {
this.attendeeTextContainer.hide();
this.attendeeComboContainer.show();
this.attendeeComboContainer.focus();
this.attendeeCombo.onTriggerClick();
},
deactivateCombo() {
this.attendeeComboContainer.hide();
this.attendeeTextContainer.show();
if (this.targetAttendeeRecord) {
const prefix = this.targetAttendeeRecord.get('status') === 'NEEDS-ACTION' ? this.app.i18n._('Set response for') + ':' : '';
const attendeeName = this.targetAttendeeRecord.get('displayText') || this.targetAttendeeRecord.get('user_id').n_fileas || '';
this.attendeeLabel.update(`${prefix}<span style="padding: 0 20px;">${attendeeName}</span>`);
}
},
getTargetAttendeeRecord() {
const selectedAttendeeId = this.attendeeCombo.getValue();
if (!selectedAttendeeId) return null;
this.targetAttendeeRecord = this.attendeeCombo.store.getById(selectedAttendeeId);
const attendeeContainers = this.iMIPrecord.get('attendeeContainersAvailable');
const attendeeList = this.getAttendeeList();
if (this.targetAttendeeRecord) {
const targetAttendeeDataFromEvent = attendeeList.find((r) => {
return r.id === selectedAttendeeId;
});
if (targetAttendeeDataFromEvent?.displaycontainer_id) {
this.targetAttendeeRecord.set('displaycontainer_id', targetAttendeeDataFromEvent.displaycontainer_id);
} else {
Object.entries(attendeeContainers).forEach((key) => {
const user = this.targetAttendeeRecord.get('user_id');
if (user && key[0].includes(user.id)) {
this.targetAttendeeRecord.set('displaycontainer_id', key[1][0]);
}
})
}
}
return this.targetAttendeeRecord;
},
getAttendeeList() {
if (this.record.get('attendee')) {
return this.record.get('attendee');
}
const myAttender = this.record.getMyAttenderRecord().data;
if (myAttender?.user_id?.id) {
myAttender.id = myAttender.user_id.id;
}
return myAttender ? [myAttender] : [];
},
/**
* show/layout iMIP panel
*/
showIMIP: function () {
this.updateInfo();
this.getLoadMask().hide();
const singleRecordPanel = this.getSingleRecordPanel();
singleRecordPanel.setVisible(true);
singleRecordPanel.setHeight(200);
singleRecordPanel.loadRecord(this.record);
},
updateInfo() {
const preconditions = this.iMIPrecord.get('preconditions');
const method = this.iMIPrecord.get('method');
const myAttenderRecord = this.record.getMyAttenderRecord();
const attenderRecord = this.getTargetAttendeeRecord() ?? myAttenderRecord;
let showActions = false;
//todo: show save action only when needed
let showSaveAction = !this.hasExistingEvent;
let text = '';
if (myAttenderRecord) {
const isInRecipientList = this.messageRecord.data['to'].find((to) => {return myAttenderRecord.get('user_id').email === to.email});
if (!isInRecipientList) {
this.descriptionField.setText(this.app.i18n._("Attention! , this is an invitation for ") + this.messageRecord.data['to'][0]['name']);
this.descriptionField.show();
}
}
// check preconditions
const uid = this.record.get('recurid') || this.record.get('uid');
if (preconditions?.[uid]) {
const precondition = preconditions[uid];
if (precondition.hasOwnProperty('EVENTEXISTS')) {
text = this.app.i18n._("The event of this message does not exist");
}
else if (precondition.hasOwnProperty('ORIGINATOR')) {
// display spam box -> might be accepted by user?
text = this.app.i18n._("The sender is not authorized to update the event");
}
else if (precondition.hasOwnProperty('RECENT')) {
text = this.app.i18n._("This message has already been processed.");
}
else if (precondition.hasOwnProperty('ATTENDEE')) {
// party crush button?
if (myAttenderRecord) {
showActions = true;
}
text = this.app.i18n._("You are not an attendee of this event");
}
else if (precondition.hasOwnProperty('NOTDELETED')) {
text = this.app.i18n._("This event has been deleted by the organizer");
}
else if (precondition.hasOwnProperty('ORGANIZER')) {
// fixme: check precondition failed, allow it in be ?
text = this.app.i18n._("You are the organizer of this event.");
showSaveAction = false;
}
else {
text = this.app.i18n._("Unsupported message");
}
}
// method specific text / actions
if (!preconditions?.[uid]) {
switch (method) {
case 'REQUEST':
if (attenderRecord) {
showActions = true;
}
if (!myAttenderRecord) {
// might happen in shared folders -> we might want to become a party crusher?
text = this.app.i18n._("This is an event invitation for someone else.");
break;
}
if (this.hasExistingEvent) {
const event = Tine.Calendar.backend.recordReader({
responseText: Ext.util.JSON.encode(this.iMIPrecord.get('event'))
});
const existingEvent = Tine.Calendar.backend.recordReader({
responseText: Ext.util.JSON.encode(this.iMIPrecord.get('existing_event'))
});
if (attenderRecord && attenderRecord.get('status') !== 'NEEDS-ACTION'
&& (event.get('external_seq') <= existingEvent.get('external_seq')
|| event.get('seq') <= existingEvent.get('seq'))) {
const attendeeName = (attenderRecord.get('user_id').n_fn ?? attenderRecord.get('user_id').name);
text = attendeeName + ' ' + this.app.i18n._("has already replied to this event invitation.");
}
if (existingEvent.isRescheduled(event)) {
text = this.app.i18n._('The event got rescheduled.');
}
}
break;
case 'REPLY':
// Someone replied => autoprocessing atm.
text = this.app.i18n._('An invited attendee responded to the invitation.');
break;
case 'CANCEL':
text = this.app.i18n._('This event has been canceled.');
this.allowViewInCalendar = false;
break;
default:
text = this.app.i18n._("Unsupported method");
this.allowViewInCalendar = false
break;
}
}
this.iMIPclause.setText(text);
this.viewInCalendarAction[(!this.allowViewInCalendar || !attenderRecord) ? 'hide' : 'show']();
this.deactivateCombo();
this.actionToolbar.doLayout();
this.statuses.each((status) => {
const freqBtn = Ext.getCmp(this.idPrefix + '-tglbtn-' + status.id);
if (!attenderRecord) {
freqBtn.hide();
} else {
if (status.id === 'NEEDS-ACTION') {
freqBtn[showSaveAction ? 'show': 'hide']();
} else {
if (showActions) {
freqBtn.show();
}
freqBtn.toggle(status.id === attenderRecord.get('status'));
}
}
})
}
});
Promise.all([Tine.Tinebase.appMgr.isInitialised('Felamimail'),
Tine.Tinebase.ApplicationStarter.isInitialised()]).then(() => {
if (Tine.Felamimail.MimeDisplayManager) {
Tine.Felamimail.MimeDisplayManager.register('text/calendar', Tine.Calendar.iMIPDetailsPanel);
}
})