Page MenuHomePhorge

No OneTemporary

Size
474 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 25a192c0..ce5db2bd 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -1,4210 +1,4209 @@
/**
* Client UI Javascript for the Calendar plugin
*
* @author Lazlo Westerhof <hello@lazlo.me>
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program 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.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
// Roundcube calendar UI client class
function rcube_calendar_ui(settings)
{
// extend base class
rcube_calendar.call(this, settings);
/*** member vars ***/
this.is_loading = false;
this.selected_event = null;
this.selected_calendar = null;
this.search_request = null;
this.saving_lock = null;
this.calendars = {};
this.quickview_sources = [];
/*** private vars ***/
var DAY_MS = 86400000;
var HOUR_MS = 3600000;
var me = this;
var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0) - (settings.dst || 0);
var client_timezone = new Date().getTimezoneOffset();
var day_clicked = day_clicked_ts = 0;
var ignore_click = false;
var event_defaults = { free_busy:'busy', alarms:'' };
var event_attendees = [];
var calendars_list;
var calenders_search_list;
var calenders_search_container;
var search_calendars = {};
var attendees_list;
var resources_list;
var resources_treelist;
var resources_data = {};
var resources_index = [];
var resource_owners = {};
var resources_events_source = { url:null, editable:false };
var freebusy_ui = { workinhoursonly:false, needsupdate:false };
var freebusy_data = {};
var current_view = null;
var count_sources = [];
var exec_deferred = bw.ie6 ? 5 : 1;
var sensitivitylabels = { 'public':rcmail.gettext('public','calendar'), 'private':rcmail.gettext('private','calendar'), 'confidential':rcmail.gettext('confidential','calendar') };
var ui_loading = rcmail.set_busy(true, 'loading');
// general datepicker settings
var datepicker_settings = {
// translate from fullcalendar format to datepicker format
dateFormat: settings['date_format'].replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
firstDay : settings['first_day'],
dayNamesMin: settings['days_short'],
monthNames: settings['months'],
monthNamesShort: settings['months'],
changeMonth: false,
showOtherMonths: true,
selectOtherMonths: true
};
// global fullcalendar settings
var fullcalendar_defaults = {
aspectRatio: 1,
ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone
monthNames : settings.months,
monthNamesShort : settings.months_short,
dayNames : settings.days,
dayNamesShort : settings.days_short,
firstDay : settings.first_day,
firstHour : settings.first_hour,
slotMinutes : 60/settings.timeslots,
timeFormat: {
'': settings.time_format,
agenda: settings.time_format + '{ - ' + settings.time_format + '}',
list: settings.time_format + '{ - ' + settings.time_format + '}',
table: settings.time_format + '{ - ' + settings.time_format + '}'
},
axisFormat : settings.time_format,
columnFormat: {
month: 'ddd', // Mon
week: 'ddd ' + settings.date_short, // Mon 9/7
day: 'dddd ' + settings.date_short, // Monday 9/7
table: settings.date_agenda
},
titleFormat: {
month: 'MMMM yyyy',
week: settings.dates_long,
day: 'dddd ' + settings['date_long'],
table: settings.dates_long
},
listPage: 7, // advance one week in agenda view
listRange: settings.agenda_range,
listSections: settings.agenda_sections,
tableCols: ['handle', 'date', 'time', 'title', 'location'],
defaultView: rcmail.env.view || settings.default_view,
allDayText: rcmail.gettext('all-day', 'calendar'),
buttonText: {
prev: '&nbsp;&#9668;&nbsp;',
next: '&nbsp;&#9658;&nbsp;',
today: settings['today'],
day: rcmail.gettext('day', 'calendar'),
week: rcmail.gettext('week', 'calendar'),
month: rcmail.gettext('month', 'calendar'),
table: rcmail.gettext('agenda', 'calendar')
},
listTexts: {
until: rcmail.gettext('until', 'calendar'),
past: rcmail.gettext('pastevents', 'calendar'),
today: rcmail.gettext('today', 'calendar'),
tomorrow: rcmail.gettext('tomorrow', 'calendar'),
thisWeek: rcmail.gettext('thisweek', 'calendar'),
nextWeek: rcmail.gettext('nextweek', 'calendar'),
thisMonth: rcmail.gettext('thismonth', 'calendar'),
nextMonth: rcmail.gettext('nextmonth', 'calendar'),
future: rcmail.gettext('futureevents', 'calendar'),
week: rcmail.gettext('weekofyear', 'calendar')
},
currentTimeIndicator: settings.time_indicator,
// event rendering
eventRender: function(event, element, view) {
if (view.name != 'list' && view.name != 'table') {
var prefix = event.sensitivity && event.sensitivity != 'public' ? String(sensitivitylabels[event.sensitivity]).toUpperCase()+': ' : '';
element.attr('title', prefix + event.title);
}
if (view.name != 'month') {
if (event.location) {
element.find('div.fc-event-title').after('<div class="fc-event-location">@&nbsp;' + Q(event.location) + '</div>');
}
if (event.sensitivity && event.sensitivity != 'public')
element.find('div.fc-event-time').append('<i class="fc-icon-sensitive"></i>');
if (event.recurrence)
element.find('div.fc-event-time').append('<i class="fc-icon-recurring"></i>');
if (event.alarms || (event.valarms && event.valarms.length))
element.find('div.fc-event-time').append('<i class="fc-icon-alarms"></i>');
}
if (event.status) {
element.addClass('cal-event-status-' + String(event.status).toLowerCase());
}
element.attr('aria-label', event.title + ', ' + me.event_date_text(event, true));
},
// render element indicating more (invisible) events
overflowRender: function(data, element) {
element.html(rcmail.gettext('andnmore', 'calendar').replace('$nr', data.count))
.click(function(e){ me.fisheye_view(data.date); });
},
// callback when a specific event is clicked
eventClick: function(event, ev, view) {
if (!event.temp && String(event.className).indexOf('fc-type-freebusy') < 0)
event_show_dialog(event, ev);
}
};
/*** imports ***/
var Q = this.quote_html;
var text2html = this.text2html;
var event_date_text = this.event_date_text;
var parse_datetime = this.parse_datetime;
var date2unixtime = this.date2unixtime;
var fromunixtime = this.fromunixtime;
var parseISO8601 = this.parseISO8601;
var date2servertime = this.date2ISO8601;
var render_message_links = this.render_message_links;
/*** private methods ***/
// same as str.split(delimiter) but it ignores delimiters within quoted strings
var explode_quoted_string = function(str, delimiter)
{
var result = [],
strlen = str.length,
q, p, i, chr, last;
for (q = p = i = 0; i < strlen; i++) {
chr = str.charAt(i);
if (chr == '"' && last != '\\') {
q = !q;
}
else if (!q && chr == delimiter) {
result.push(str.substring(p, i));
p = i + 1;
}
last = chr;
}
result.push(str.substr(p));
return result;
};
// Change the first charcter to uppercase
var ucfirst = function(str)
{
return str.charAt(0).toUpperCase() + str.substr(1);
};
// clone the given date object and optionally adjust time
var clone_date = function(date, adjust)
{
var d = new Date(date.getTime());
// set time to 00:00
if (adjust == 1) {
d.setHours(0);
d.setMinutes(0);
}
// set time to 23:59
else if (adjust == 2) {
d.setHours(23);
d.setMinutes(59);
}
return d;
};
// fix date if jumped over a DST change
var fix_date = function(date)
{
if (date.getHours() == 23)
date.setTime(date.getTime() + HOUR_MS);
else if (date.getHours() > 0)
date.setHours(0);
};
var date2timestring = function(date, dateonly)
{
return date2servertime(date).replace(/[^0-9]/g, '').substr(0, (dateonly ? 8 : 14));
}
var format_datetime = function(date, mode, voice)
{
return me.format_datetime(date, mode, voice);
}
var render_link = function(url)
{
var islink = false, href = url;
if (url.match(/^[fhtpsmailo]+?:\/\//i)) {
islink = true;
}
else if (url.match(/^[a-z0-9.-:]+(\/|$)/i)) {
islink = true;
href = 'http://' + url;
}
return islink ? '<a href="' + Q(href) + '" target="_blank">' + Q(url) + '</a>' : Q(url);
}
// determine whether the given date is on a weekend
var is_weekend = function(date)
{
return date.getDay() == 0 || date.getDay() == 6;
};
var is_workinghour = function(date)
{
if (settings['work_start'] > settings['work_end'])
return date.getHours() >= settings['work_start'] || date.getHours() < settings['work_end'];
else
return date.getHours() >= settings['work_start'] && date.getHours() < settings['work_end'];
};
// check if the event has 'real' attendees, excluding the current user
var has_attendees = function(event)
{
return (event.attendees && event.attendees.length && (event.attendees.length > 1 || String(event.attendees[0].email).toLowerCase() != settings.identity.email));
};
// check if the current user is an attendee of this event
var is_attendee = function(event, role, email)
{
var emails = email ? ';'+email.toLowerCase() : settings.identity.emails;
for (var i=0; event.attendees && i < event.attendees.length; i++) {
if ((!role || event.attendees[i].role == role) && event.attendees[i].email && emails.indexOf(';'+event.attendees[i].email.toLowerCase()) >= 0)
return event.attendees[i];
}
return false;
};
// check if the current user is the organizer
var is_organizer = function(event, email)
{
return is_attendee(event, 'ORGANIZER', email) || !event.id;
};
/**
* Check permissions on the given calendar object
*/
var has_permission = function(cal, perm)
{
// multiple chars means "either of"
if (String(perm).length > 1) {
for (var i=0; i < perm.length; i++) {
if (has_permission(cal, perm[i]))
return true;
}
}
if (cal.rights && String(cal.rights).indexOf(perm) >= 0) {
return true;
}
return (perm == 'i' && cal.editable) || (perm == 'v' && cal.editable);
}
var load_attachment = function(event, att)
{
var query = { _id: att.id, _event: event.recurrence_id || event.id, _cal:event.calendar, _frame: 1 };
if (event.rev)
query._rev = event.rev;
if (event.calendar == "--invitation--itip")
$.extend(query, {_uid: event._uid, _part: event._part, _mbox: event._mbox});
// open attachment in frame if it's of a supported mimetype
if (id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
if (rcmail.open_window(rcmail.url('get-attachment', query), true, true)) {
return;
}
}
query._frame = null;
query._download = 1;
rcmail.goto_url('get-attachment', query, false);
};
// build event attachments list
var event_show_attachments = function(list, container, event, edit)
{
var i, id, len, img, content, li, elem,
ul = document.createElement('UL');
ul.className = 'attachmentslist';
for (i=0, len=list.length; i<len; i++) {
elem = list[i];
li = document.createElement('LI');
li.className = elem.classname;
if (edit) {
rcmail.env.attachments[elem.id] = elem;
// delete icon
content = $('<a href="#delete" />')
.attr('title', rcmail.gettext('delete'))
.attr('aria-label', rcmail.gettext('delete') + ' ' + Q(elem.name))
.addClass('delete')
.click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
if (!rcmail.env.deleteicon)
content.html(rcmail.gettext('delete'));
else {
img = document.createElement('IMG');
img.src = rcmail.env.deleteicon;
img.alt = rcmail.gettext('delete');
content.append(img);
}
content.appendTo(li);
}
// name/link
content = $('<a href="#load" />')
.html(Q(elem.name))
.addClass('file')
.click({event: event, att: elem}, function(e) {
load_attachment(e.data.event, e.data.att);
return false;
})
.appendTo(li);
ul.appendChild(li);
}
if (edit && rcmail.gui_objects.attachmentlist) {
ul.id = rcmail.gui_objects.attachmentlist.id;
rcmail.gui_objects.attachmentlist = ul;
}
container.empty().append(ul);
};
var remove_attachment = function(elem, id)
{
$(elem.parentNode).hide();
rcmail.env.deleted_attachments.push(id);
delete rcmail.env.attachments[id];
};
// event details dialog (show only)
var event_show_dialog = function(event, ev, temp)
{
var $dialog = $("#eventshow");
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false, rights:'lrs' };
if (!temp)
me.selected_event = event;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
// remove status-* and sensitivity-* classes
$dialog.removeClass(function(i, oldclass) {
var oldies = String(oldclass).split(' ');
return $.grep(oldies, function(cls) { return cls.indexOf('status-') === 0 || cls.indexOf('sensitivity-') === 0 }).join(' ');
});
// convert start/end dates if not done yet by fullcalendar
if (typeof event.start == 'string')
event.start = parseISO8601(event.start);
if (typeof event.end == 'string')
event.end = parseISO8601(event.end);
// allow other plugins to do actions when event form is opened
rcmail.triggerEvent('calendar-event-init', {o: event});
$dialog.find('div.event-section, div.event-line').hide();
$('#event-title').html(Q(event.title)).show();
if (event.location)
$('#event-location').html('@ ' + text2html(event.location)).show();
if (event.description)
$('#event-description').show().children('.event-text').html(text2html(event.description, 300, 6));
if (event.vurl)
$('#event-url').show().children('.event-text').html(render_link(event.vurl));
// render from-to in a nice human-readable way
// -> now shown in dialog title
// $('#event-date').html(Q(me.event_date_text(event))).show();
if (event.recurrence && event.recurrence_text)
$('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
if (event.valarms && event.alarms_text)
$('#event-alarm').show().children('.event-text').html(Q(event.alarms_text));
if (calendar.name)
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).attr('class', 'event-text cal-'+calendar.id).css('color', calendar.textColor || calendar.color || '');
if (event.categories)
$('#event-category').show().children('.event-text').html(Q(event.categories)).attr('class', 'event-text cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, ''));
if (event.free_busy)
$('#event-free-busy').show().children('.event-text').html(Q(rcmail.gettext(event.free_busy, 'calendar')));
if (event.priority > 0) {
var priolabels = [ '', rcmail.gettext('highest'), rcmail.gettext('high'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
$('#event-priority').show().children('.event-text').html(Q(event.priority+' '+priolabels[event.priority]));
}
if (event.status) {
var status_lc = String(event.status).toLowerCase();
$('#event-status').show().children('.event-text').text(rcmail.gettext('status-'+status_lc,'calendar'));
$('#event-status-badge > span').text(rcmail.gettext('status-'+status_lc,'calendar'));
$dialog.addClass('status-'+status_lc);
}
if (event.sensitivity && event.sensitivity != 'public') {
$('#event-sensitivity').show().children('.event-text').text(sensitivitylabels[event.sensitivity]);
$('#event-status-badge > span').text(sensitivitylabels[event.sensitivity]);
$dialog.addClass('sensitivity-'+event.sensitivity);
}
if (event.created || event.changed) {
var created = parseISO8601(event.created),
changed = parseISO8601(event.changed)
$('#event-created-changed .event-created').html(Q(created ? format_datetime(created) : rcmail.gettext('unknown','calendar')))
$('#event-created-changed .event-changed').html(Q(changed ? format_datetime(changed) : rcmail.gettext('unknown','calendar')))
$('#event-created-changed').show()
}
// create attachments list
if ($.isArray(event.attachments)) {
event_show_attachments(event.attachments, $('#event-attachments').children('.event-text'), event);
if (event.attachments.length > 0) {
$('#event-attachments').show();
}
}
else if (calendar.attachments) {
// fetch attachments, some drivers doesn't set 'attachments' prop of the event?
}
// build attachments list
$('#event-links').hide();
if ($.isArray(event.links) && event.links.length) {
render_message_links(event.links || [], $('#event-links').children('.event-text'), false, 'calendar');
$('#event-links').show();
}
// list event attendees
if (calendar.attendees && event.attendees) {
// sort resources to the end
event.attendees.sort(function(a,b) {
var j = a.cutype == 'RESOURCE' ? 1 : 0,
k = b.cutype == 'RESOURCE' ? 1 : 0;
return (j - k);
});
var data, organizer, mystatus = null, rsvp, line, morelink, html = '', overflow = '';
for (var j=0; j < event.attendees.length; j++) {
data = event.attendees[j];
if (data.email) {
if (data.role == 'ORGANIZER')
organizer = true;
else if (settings.identity.emails.indexOf(';'+data.email) >= 0) {
mystatus = data.status.toLowerCase();
if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
rsvp = mystatus;
}
}
line = rcube_libcalendaring.attendee_html(data);
if (morelink)
overflow += line;
else
html += line;
// stop listing attendees
if (j == 7 && event.attendees.length >= 7) {
morelink = $('<a href="#more" class="morelink"></a>').html(rcmail.gettext('andnmore', 'calendar').replace('$nr', event.attendees.length - j - 1));
}
}
if (html && (event.attendees.length > 1 || !organizer)) {
$('#event-attendees').show()
.children('.event-text')
.html(html)
.find('a.mailtolink').click(event_attendee_click);
// display all attendees in a popup when clicking the "more" link
if (morelink) {
$('#event-attendees .event-text').append(morelink);
morelink.click(function(e){
rcmail.show_popup_dialog(
'<div id="all-event-attendees" class="event-attendees">' + html + overflow + '</div>',
rcmail.gettext('tabattendees','calendar'),
null,
{ width:450, modal:false });
$('#all-event-attendees a.mailtolink').click(event_attendee_click);
return false;
})
}
}
if (mystatus && !rsvp) {
$('#event-partstat').show().children('.changersvp')
.removeClass('accepted tentative declined delegated needs-action')
.addClass(mystatus)
.children('.event-text')
.text(rcmail.gettext('status' + mystatus, 'libcalendaring'));
}
var show_rsvp = rsvp && !is_organizer(event) && event.status != 'CANCELLED' && has_permission(calendar, 'v');
$('#event-rsvp')[(show_rsvp ? 'show' : 'hide')]();
$('#event-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+mystatus+']').prop('disabled', true);
if (show_rsvp && event.comment)
$('#event-rsvp-comment').show().children('.event-text').html(Q(event.comment));
$('#event-rsvp a.reply-comment-toggle').show();
$('#event-rsvp .itip-reply-comment textarea').hide().val('');
if (event.recurrence && event.id) {
var sel = event._savemode || (event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all'));
$('#event-rsvp .rsvp-buttons').addClass('recurring');
}
else {
$('#event-rsvp .rsvp-buttons').removeClass('recurring');
}
}
var buttons = [];
if (!temp && calendar.editable && event.editable !== false) {
buttons.push({
text: rcmail.gettext('edit', 'calendar'),
click: function() {
event_edit_dialog('edit', event);
}
});
}
if (!temp && has_permission(calendar, 'td') && event.editable !== false) {
buttons.push({
text: rcmail.gettext('delete', 'calendar'),
'class': 'delete',
click: function() {
me.delete_event(event);
$dialog.dialog('close');
}
});
}
if (!buttons.length) {
buttons.push({
text: rcmail.gettext('close', 'calendar'),
click: function(){
$dialog.dialog('close');
}
});
}
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: me.event_date_text(event),
open: function() {
$dialog.attr('aria-hidden', 'false');
setTimeout(function(){
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
}, 5);
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
rcmail.command('menu-close','eventoptionsmenu');
$('.libcal-rsvp-replymode').hide();
},
dragStart: function() {
rcmail.command('menu-close','eventoptionsmenu');
$('.libcal-rsvp-replymode').hide();
},
resizeStart: function() {
rcmail.command('menu-close','eventoptionsmenu');
$('.libcal-rsvp-replymode').hide();
},
buttons: buttons,
minWidth: 320,
width: 420
}).show();
// remember opener element (to be focused on close)
$dialog.data('opener', ev && rcube_event.is_keyboard(ev) ? ev.target : null);
// set voice title on dialog widget
$dialog.dialog('widget').removeAttr('aria-labelledby')
.attr('aria-label', me.event_date_text(event, true) + ', ', event.title);
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 420);
// add link for "more options" drop-down
if (!temp && !event.temporary && event.calendar != '_resource') {
$('<a>')
.attr('href', '#')
.html(rcmail.gettext('eventoptions','calendar'))
.addClass('dropdown-link')
.click(function(e) {
return rcmail.command('menu-open','eventoptionsmenu', this, e)
})
.appendTo($dialog.parent().find('.ui-dialog-buttonset'));
}
rcmail.enable_command('event-history', calendar.history)
};
// event handler for clicks on an attendee link
var event_attendee_click = function(e)
{
var cutype = $(this).attr('data-cutype'),
mailto = this.href.substr(7);
if (rcmail.env.calendar_resources && cutype == 'RESOURCE') {
event_resources_dialog(mailto);
}
else {
rcmail.command('compose', mailto, e ? e.target : null, e);
}
return false;
};
// bring up the event dialog (jquery-ui popup)
var event_edit_dialog = function(action, event)
{
// copy opener element from show dialog
var op_elem = $("#eventshow:ui-dialog").data('opener');
// close show dialog first
$("#eventshow:ui-dialog").data('opener', null).dialog('close');
var $dialog = $('<div>');
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:true, rights: action=='new' ? 'lrwitd' : 'lrs' };
me.selected_event = $.extend($.extend({}, event_defaults), event); // clone event object (with defaults)
event = me.selected_event; // change reference to clone
freebusy_ui.needsupdate = false;
// reset dialog first
$('#eventtabs').get(0).reset();
$('#event-panel-recurrence input, #event-panel-recurrence select, #event-panel-attachments input').prop('disabled', false);
$('#event-panel-recurrence, #event-panel-attachments').removeClass('disabled');
// allow other plugins to do actions when event form is opened
rcmail.triggerEvent('calendar-event-init', {o: event});
// event details
var title = $('#edit-title').val(event.title || '');
var location = $('#edit-location').val(event.location || '');
var description = $('#edit-description').text(event.description || '');
var vurl = $('#edit-url').val(event.vurl || '');
var categories = $('#edit-categories').val(event.categories);
var calendars = $('#edit-calendar').val(event.calendar);
var eventstatus = $('#edit-event-status').val(event.status);
var freebusy = $('#edit-free-busy').val(event.free_busy);
var priority = $('#edit-priority').val(event.priority);
var sensitivity = $('#edit-sensitivity').val(event.sensitivity);
var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
var startdate = $('#edit-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
var starttime = $('#edit-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
var enddate = $('#edit-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
var endtime = $('#edit-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
var allday = $('#edit-allday').get(0);
var notify = $('#edit-attendees-donotify').get(0);
var invite = $('#edit-attendees-invite').get(0);
var comment = $('#edit-attendees-comment');
invite.checked = settings.itip_notify & 1 > 0;
notify.checked = has_attendees(event) && invite.checked;
if (event.allDay) {
starttime.val("12:00").hide();
endtime.val("13:00").hide();
allday.checked = true;
}
else {
allday.checked = false;
}
// set calendar selection according to permissions
calendars.find('option').each(function(i, opt) {
var cal = me.calendars[opt.value] || {};
$(opt).prop('disabled', !(cal.editable || (action == 'new' && has_permission(cal, 'i'))))
});
// set alarm(s)
me.set_alarms_edit('#edit-alarms', action != 'new' && event.valarms && calendar.alarms ? event.valarms : []);
// enable/disable alarm property according to backend support
$('#edit-alarms')[(calendar.alarms ? 'show' : 'hide')]();
// check categories drop-down: add value if not exists
if (event.categories && !categories.find("option[value='"+event.categories+"']").length) {
$('<option>').attr('value', event.categories).text(event.categories).appendTo(categories).prop('selected', true);
}
if ($.isArray(event.links) && event.links.length) {
render_message_links(event.links, $('#edit-event-links .event-text'), true, 'calendar');
$('#edit-event-links').show();
}
else {
$('#edit-event-links').hide();
}
// show warning if editing a recurring event
if (event.id && event.recurrence) {
var sel = event._savemode || (event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all'));
$('#edit-recurring-warning').show();
$('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true).change();
}
else
$('#edit-recurring-warning').hide();
// init attendees tab
var organizer = !event.attendees || is_organizer(event),
allow_invitations = organizer || (calendar.owner && calendar.owner == 'anonymous') || settings.invite_shared;
event_attendees = [];
attendees_list = $('#edit-attendees-table > tbody').html('');
resources_list = $('#edit-resources-table > tbody').html('');
$('#edit-attendees-notify')[(action != 'new' && allow_invitations && has_attendees(event) && (settings.itip_notify & 2) ? 'show' : 'hide')]();
$('#edit-localchanges-warning')[(action != 'new' && has_attendees(event) && !(allow_invitations || (calendar.owner && is_organizer(event, calendar.owner))) ? 'show' : 'hide')]();
var load_attendees_tab = function()
{
var j, data, reply_selected = 0;
if (event.attendees) {
for (j=0; j < event.attendees.length; j++) {
data = event.attendees[j];
// reset attendee status
if (event._savemode == 'new' && data.role != 'ORGANIZER') {
data.status = 'NEEDS-ACTION';
delete data.noreply;
}
add_attendee(data, !allow_invitations);
if (allow_invitations && data.role != 'ORGANIZER' && !data.noreply)
reply_selected++;
}
}
// make sure comment box is visible if at least one attendee has reply enabled
// or global "send invitations" checkbox is checked
$('#eventedit .attendees-commentbox')[(reply_selected || invite.checked ? 'show' : 'hide')]();
// select the correct organizer identity
var identity_id = 0;
$.each(settings.identities, function(i,v){
if (organizer && typeof organizer == 'object' && v == organizer.email) {
identity_id = i;
return false;
}
});
$('#edit-identities-list').val(identity_id);
$('#edit-attendees-form')[(allow_invitations?'show':'hide')]();
$('#edit-attendee-schedule')[(calendar.freebusy?'show':'hide')]();
};
// attachments
var load_attachments_tab = function()
{
rcmail.enable_command('remove-attachment', calendar.editable && !event.recurrence_id);
rcmail.env.deleted_attachments = [];
// we're sharing some code for uploads handling with app.js
rcmail.env.attachments = [];
rcmail.env.compose_id = event.id; // for rcmail.async_upload_form()
if ($.isArray(event.attachments)) {
event_show_attachments(event.attachments, $('#edit-attachments'), event, true);
}
else {
$('#edit-attachments > ul').empty();
// fetch attachments, some drivers doesn't set 'attachments' array for event?
}
};
// init dialog buttons
var buttons = [];
// save action
buttons.push({
text: rcmail.gettext('save', 'calendar'),
'class': 'mainaction',
click: function() {
var start = parse_datetime(allday.checked ? '12:00' : starttime.val(), startdate.val());
var end = parse_datetime(allday.checked ? '13:00' : endtime.val(), enddate.val());
// basic input validatetion
if (start.getTime() > end.getTime()) {
alert(rcmail.gettext('invalideventdates', 'calendar'));
return false;
}
// post data to server
var data = {
calendar: event.calendar,
start: date2servertime(start),
end: date2servertime(end),
allday: allday.checked?1:0,
title: title.val(),
description: description.val(),
location: location.val(),
categories: categories.val(),
vurl: vurl.val(),
free_busy: freebusy.val(),
priority: priority.val(),
sensitivity: sensitivity.val(),
status: eventstatus.val(),
recurrence: me.serialize_recurrence(endtime.val()),
valarms: me.serialize_alarms('#edit-alarms'),
attendees: event_attendees,
links: me.selected_event.links,
deleted_attachments: rcmail.env.deleted_attachments,
attachments: []
};
// uploaded attachments list
for (var i in rcmail.env.attachments)
if (i.match(/^rcmfile(.+)/))
data.attachments.push(RegExp.$1);
// read attendee roles
$('select.edit-attendee-role').each(function(i, elem){
if (data.attendees[i])
data.attendees[i].role = $(elem).val();
});
if (organizer)
data._identity = $('#edit-identities-list option:selected').val();
// per-attendee notification suppression
var need_invitation = false;
if (allow_invitations) {
$.each(data.attendees, function (i, v) {
if (v.role != 'ORGANIZER') {
if ($('input.edit-attendee-reply[value="' + v.email + '"]').prop('checked') || v.cutype == 'RESOURCE') {
need_invitation = true;
delete data.attendees[i]['noreply'];
}
else if (settings.itip_notify > 0) {
data.attendees[i].noreply = 1;
}
}
});
}
// tell server to send notifications
if ((data.attendees.length || (event.id && event.attendees.length)) && allow_invitations && (notify.checked || invite.checked || need_invitation)) {
data._notify = settings.itip_notify;
data._comment = comment.val();
}
data.calendar = calendars.val();
if (event.id) {
data.id = event.id;
if (event.recurrence)
data._savemode = $('input.edit-recurring-savemode:checked').val();
if (data.calendar && data.calendar != event.calendar)
data._fromcalendar = event.calendar;
}
update_event(action, data);
$dialog.dialog("close");
} // end click:
});
if (event.id) {
buttons.push({
text: rcmail.gettext('delete', 'calendar'),
'class': 'delete',
click: function() {
me.delete_event(event);
$dialog.dialog('close');
}
});
}
buttons.push({
text: rcmail.gettext('cancel', 'calendar'),
click: function() {
$dialog.dialog("close");
}
});
// show/hide tabs according to calendar's feature support
$('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
$('#edit-tab-resources')[(rcmail.env.calendar_resources?'show':'hide')]();
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
// activate the first tab
$('#eventtabs').tabs('option', 'active', 0);
// hack: set task to 'calendar' to make all dialog actions work correctly
var comm_path_before = rcmail.env.comm_path;
rcmail.env.comm_path = comm_path_before.replace(/_task=[a-z]+/, '_task=calendar');
var editform = $("#eventedit");
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
closeOnEscape: false,
title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
open: function() {
editform.attr('aria-hidden', 'false');
},
close: function() {
editform.hide().attr('aria-hidden', 'true').appendTo(document.body);
$dialog.dialog("destroy").remove();
rcmail.ksearch_blur();
freebusy_data = {};
rcmail.env.comm_path = comm_path_before; // restore comm_path
if (op_elem)
$(op_elem).focus();
},
buttons: buttons,
minWidth: 500,
width: 600
}).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE6
// set dialog size according to form content
me.dialog_resize($dialog.get(0), editform.height() + (bw.ie ? 20 : 0), 550);
title.select();
// init other tabs asynchronously
window.setTimeout(function(){ me.set_recurrence_edit(event); }, exec_deferred);
if (calendar.attendees)
window.setTimeout(load_attendees_tab, exec_deferred);
if (calendar.attachments)
window.setTimeout(load_attachments_tab, exec_deferred);
};
// show event changelog in a dialog
var event_history_dialog = function(event)
{
if (!event.id || !window.libkolab_audittrail)
return false
// render dialog
var $dialog = libkolab_audittrail.object_history_dialog({
module: 'calendar',
container: '#eventhistory',
title: rcmail.gettext('objectchangelog','calendar') + ' - ' + event.title + ', ' + me.event_date_text(event),
// callback function for list actions
listfunc: function(action, rev) {
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
rcmail.http_post('event', { action:action, e:{ id:event.id, calendar:event.calendar, rev: rev } }, me.loading_lock);
},
// callback function for comparing two object revisions
comparefunc: function(rev1, rev2) {
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
rcmail.http_post('event', { action:'diff', e:{ id:event.id, calendar:event.calendar, rev1: rev1, rev2: rev2 } }, me.loading_lock);
}
});
$dialog.data('event', event);
// fetch changelog data
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
rcmail.http_post('event', { action:'changelog', e:{ id:event.id, calendar:event.calendar } }, me.loading_lock);
};
// callback from server with changelog data
var render_event_changelog = function(data)
{
var $dialog = $('#eventhistory'),
event = $dialog.data('event');
if (data === false || !data.length || !event) {
// display 'unavailable' message
$('<div class="notfound-message event-dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','calendar') + '</div>')
.insertBefore($dialog.find('.changelog-table').hide());
return;
}
data.module = 'calendar';
libkolab_audittrail.render_changelog(data, event, me.calendars[event.calendar]);
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 600);
};
// callback from server with event diff data
var event_show_diff = function(data)
{
var event = me.selected_event,
$dialog = $("#eventdiff");
$dialog.find('div.event-section, div.event-line, h1.event-title-new').hide().data('set', false).find('.index').html('');
$dialog.find('div.event-section.clone, div.event-line.clone').remove();
// always show event title and date
$('.event-title', $dialog).text(event.title).removeClass('event-text-old').show();
$('.event-date', $dialog).text(me.event_date_text(event)).show();
// show each property change
$.each(data.changes, function(i,change) {
var prop = change.property, r2, html = false,
row = $('div.event-' + prop, $dialog).first();
// special case: title
if (prop == 'title') {
$('.event-title', $dialog).addClass('event-text-old').text(change['old'] || '--');
$('.event-title-new', $dialog).text(change['new'] || '--').show();
}
// no display container for this property
if (!row.length) {
return true;
}
// clone row if already exists
if (row.data('set')) {
r2 = row.clone().addClass('clone').insertAfter(row);
row = r2;
}
// format dates
if (['start','end','changed'].indexOf(prop) >= 0) {
if (change['old']) change.old_ = me.format_datetime(parseISO8601(change['old']));
if (change['new']) change.new_ = me.format_datetime(parseISO8601(change['new']));
}
// render description text
else if (prop == 'description') {
// TODO: show real text diff
if (!change.diff_ && change['old']) change.old_ = text2html(change['old']);
if (!change.diff_ && change['new']) change.new_ = text2html(change['new']);
html = true;
}
// format attendees struct
else if (prop == 'attendees') {
if (change['old']) change.old_ = rcube_libcalendaring.attendee_html(change['old']);
if (change['new']) change.new_ = rcube_libcalendaring.attendee_html($.extend({}, change['old'] || {}, change['new']));
html = true;
}
// localize priority values
else if (prop == 'priority') {
var priolabels = [ '', rcmail.gettext('highest'), rcmail.gettext('high'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
if (change['old']) change.old_ = change['old'] + ' ' + (priolabels[change['old']] || '');
if (change['new']) change.new_ = change['new'] + ' ' + (priolabels[change['new']] || '');
}
// localize status
else if (prop == 'status') {
var status_lc = String(event.status).toLowerCase();
if (change['old']) change.old_ = rcmail.gettext(String(change['old']).toLowerCase(), 'calendar');
if (change['new']) change.new_ = rcmail.gettext(String(change['new']).toLowerCase(), 'calendar');
}
// format attachments struct
if (prop == 'attachments') {
if (change['old']) event_show_attachments([change['old']], row.children('.event-text-old'), event, false);
else row.children('.event-text-old').text('--');
if (change['new']) event_show_attachments([$.extend({}, change['old'] || {}, change['new'])], row.children('.event-text-new'), event, false);
else row.children('.event-text-new').text('--');
// remove click handler as we're currentyl not able to display the according attachment contents
$('.attachmentslist li a', row).unbind('click').removeAttr('href');
}
else if (change.diff_) {
row.children('.event-text-diff').html(change.diff_);
row.children('.event-text-old, .event-text-new').hide();
}
else {
if (!html) {
// escape HTML characters
change.old_ = Q(change.old_ || change['old'] || '--')
change.new_ = Q(change.new_ || change['new'] || '--')
}
row.children('.event-text-old').html(change.old_ || change['old'] || '--');
row.children('.event-text-new').html(change.new_ || change['new'] || '--');
}
// display index number
if (typeof change.index != 'undefined') {
row.find('.index').html('(' + change.index + ')');
}
row.show().data('set', true);
// hide event-date line
if (prop == 'start' || prop == 'end')
$('.event-date', $dialog).hide();
});
var buttons = {};
buttons[rcmail.gettext('close', 'calendar')] = function() {
$dialog.dialog('close');
};
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('objectdiff','calendar').replace('$rev1', data.rev1).replace('$rev2', data.rev2) + ' - ' + event.title,
open: function() {
$dialog.attr('aria-hidden', 'false');
setTimeout(function(){
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
}, 5);
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
},
buttons: buttons,
minWidth: 320,
width: 450
}).show();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 400);
};
// close the event history dialog
var close_history_dialog = function()
{
$('#eventhistory, #eventdiff').each(function(i, elem) {
var $dialog = $(elem);
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
});
}
// exports
this.event_show_diff = event_show_diff;
this.event_show_dialog = event_show_dialog;
this.event_history_dialog = event_history_dialog;
this.render_event_changelog = render_event_changelog;
this.close_history_dialog = close_history_dialog;
// open a dialog to display detailed free-busy information and to find free slots
var event_freebusy_dialog = function()
{
var $dialog = $('#eventfreebusy'),
event = me.selected_event;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (!event_attendees.length)
return false;
// set form elements
var allday = $('#edit-allday').get(0);
var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
freebusy_ui.startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
freebusy_ui.starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
freebusy_ui.enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
freebusy_ui.endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
if (allday.checked) {
freebusy_ui.starttime.val("12:00").hide();
freebusy_ui.endtime.val("13:00").hide();
event.allDay = true;
}
// read attendee roles from drop-downs
$('select.edit-attendee-role').each(function(i, elem){
if (event_attendees[i])
event_attendees[i].role = $(elem).val();
});
// render time slots
var now = new Date(), fb_start = new Date(), fb_end = new Date();
fb_start.setTime(event.start);
fb_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0);
fb_end.setTime(fb_start.getTime() + DAY_MS);
freebusy_data = { required:{}, all:{} };
freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet
freebusy_ui.numdays = Math.max(allday.checked ? 14 : 1, Math.ceil(duration * 2 / 86400));
freebusy_ui.interval = allday.checked ? 1440 : 60;
freebusy_ui.start = fb_start;
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays);
render_freebusy_grid(0);
// render list of attendees
freebusy_ui.attendees = {};
var domid, dispname, data, role_html, list_html = '';
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
dispname = Q(data.name || data.email);
domid = String(data.email).replace(rcmail.identifier_expr, '');
role_html = '<a class="attendee-role-toggle" id="rcmlia' + domid + '" title="' + Q(rcmail.gettext('togglerole', 'calendar')) + '">&nbsp;</a>';
list_html += '<div class="attendee ' + String(data.role).toLowerCase() + '" id="rcmli' + domid + '">' + role_html + dispname + '</div>';
// clone attendees data for local modifications
freebusy_ui.attendees[i] = freebusy_ui.attendees[domid] = $.extend({}, data);
}
// add total row
list_html += '<div class="attendee spacer">&nbsp;</div>';
list_html += '<div class="attendee total">' + rcmail.gettext('reqallattendees','calendar') + '</div>';
$('#schedule-attendees-list').html(list_html)
.unbind('click.roleicons')
.bind('click.roleicons', function(e){
// toggle attendee status upon click on icon
if (e.target.id && e.target.id.match(/rcmlia(.+)/)) {
var attendee, domid = RegExp.$1,
roles = [ 'REQ-PARTICIPANT', 'OPT-PARTICIPANT', 'NON-PARTICIPANT', 'CHAIR' ];
if ((attendee = freebusy_ui.attendees[domid]) && attendee.role != 'ORGANIZER') {
var req = attendee.role != 'OPT-PARTICIPANT' && attendee.role != 'NON-PARTICIPANT';
var j = $.inArray(attendee.role, roles);
j = (j+1) % roles.length;
attendee.role = roles[j];
$(e.target).parent().attr('class', 'attendee '+String(attendee.role).toLowerCase());
// update total display if required-status changed
if (req != (roles[j] != 'OPT-PARTICIPANT' && roles[j] != 'NON-PARTICIPANT')) {
compute_freebusy_totals();
update_freebusy_display(attendee.email);
}
}
}
return false;
});
// enable/disable buttons
$('#shedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime()));
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('select', 'calendar')] = function() {
$('#edit-startdate').val(freebusy_ui.startdate.val());
$('#edit-starttime').val(freebusy_ui.starttime.val());
$('#edit-enddate').val(freebusy_ui.enddate.val());
$('#edit-endtime').val(freebusy_ui.endtime.val());
// write role changes back to main dialog
$('select.edit-attendee-role').each(function(i, elem){
if (event_attendees[i] && freebusy_ui.attendees[i]) {
event_attendees[i].role = freebusy_ui.attendees[i].role;
$(elem).val(event_attendees[i].role);
}
});
if (freebusy_ui.needsupdate)
update_freebusy_status(me.selected_event);
freebusy_ui.needsupdate = false;
$dialog.dialog("close");
};
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: (!bw.ie6 && !bw.ie7),
title: rcmail.gettext('scheduletime', 'calendar'),
open: function() {
rcmail.ksearch_blur();
$dialog.attr('aria-hidden', 'false').find('#shedule-find-next, #shedule-find-prev').not(':disabled').first().focus();
},
close: function() {
if (bw.ie6)
$("#edit-attendees-table").css('visibility','visible');
$dialog.dialog("destroy").attr('aria-hidden', 'true').hide();
// TODO: focus opener button
},
resizeStop: function() {
render_freebusy_overlay();
},
buttons: buttons,
minWidth: 640,
width: 850
}).show();
// hide edit dialog on IE6 because of drop-down elements
if (bw.ie6)
$("#edit-attendees-table").css('visibility','hidden');
// adjust dialog size to fit grid without scrolling
var gridw = $('#schedule-freebusy-times').width();
var overflow = gridw - $('#attendees-freebusy-table td.times').width() + 1;
me.dialog_resize($dialog.get(0), $dialog.height() + (bw.ie ? 20 : 0), 800 + Math.max(0, overflow));
// fetch data from server
freebusy_ui.loading = 0;
load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
};
// render an HTML table showing free-busy status for all the event attendees
var render_freebusy_grid = function(delta)
{
if (delta) {
freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
fix_date(freebusy_ui.start);
// skip weekends if in workinhoursonly-mode
if (Math.abs(delta) == 1 && freebusy_ui.workinhoursonly) {
while (is_weekend(freebusy_ui.start))
freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
fix_date(freebusy_ui.start);
}
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays);
}
var dayslots = Math.floor(1440 / freebusy_ui.interval);
var date_format = 'ddd '+ (dayslots <= 2 ? settings.date_short : settings.date_format);
var lastdate, datestr, css,
curdate = new Date(),
allday = (freebusy_ui.interval == 1440),
times_css = (allday ? 'allday ' : ''),
dates_row = '<tr class="dates">',
times_row = '<tr class="times">',
slots_row = '';
for (var s = 0, t = freebusy_ui.start.getTime(); t < freebusy_ui.end.getTime(); s++) {
curdate.setTime(t);
datestr = fc.fullCalendar('formatDate', curdate, date_format);
if (datestr != lastdate) {
if (lastdate && !allday) break;
dates_row += '<th colspan="' + dayslots + '" class="boxtitle date' + $.fullCalendar.formatDate(curdate, 'ddMMyyyy') + '">' + Q(datestr) + '</th>';
lastdate = datestr;
}
// set css class according to working hours
css = is_weekend(curdate) || (freebusy_ui.interval <= 60 && !is_workinghour(curdate)) ? 'offhours' : 'workinghours';
times_row += '<td class="' + times_css + css + '" id="t-' + Math.floor(t/1000) + '">' + Q(allday ? rcmail.gettext('all-day','calendar') : $.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
slots_row += '<td class="' + css + ' unknown">&nbsp;</td>';
t += freebusy_ui.interval * 60000;
}
dates_row += '</tr>';
times_row += '</tr>';
// render list of attendees
var domid, data, list_html = '', times_html = '';
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
domid = String(data.email).replace(rcmail.identifier_expr, '');
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
}
// add line for all/required attendees
times_html += '<tr class="spacer"><td colspan="' + (dayslots * freebusy_ui.numdays) + '">&nbsp;</td>';
times_html += '<tr id="fbrowall">' + slots_row + '</tr>';
var table = $('#schedule-freebusy-times');
table.children('thead').html(dates_row + times_row);
table.children('tbody').html(times_html);
// initialize event handlers on grid
if (!freebusy_ui.grid_events) {
freebusy_ui.grid_events = true;
table.children('thead').click(function(e){
// move event to the clicked date/time
if (e.target.id && e.target.id.match(/t-(\d+)/)) {
var newstart = new Date(RegExp.$1 * 1000);
// set time to 00:00
if (me.selected_event.allDay) {
newstart.setMinutes(0);
newstart.setHours(0);
}
update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000));
render_freebusy_overlay();
}
})
}
// if we have loaded free-busy data, show it
if (!freebusy_ui.loading) {
if (freebusy_ui.start < freebusy_data.start || freebusy_ui.end > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) {
load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
}
else {
for (var email, i=0; i < event_attendees.length; i++) {
if ((email = event_attendees[i].email))
update_freebusy_display(email);
}
}
}
// render current event date/time selection over grid table
// use timeout to let the dom attributes (width/height/offset) be set first
window.setTimeout(function(){ render_freebusy_overlay(); }, 10);
};
// render overlay element over the grid to visiualize the current event date/time
var render_freebusy_overlay = function()
{
var overlay = $('#schedule-event-time');
if (me.selected_event.end.getTime() <= freebusy_ui.start.getTime() || me.selected_event.start.getTime() >= freebusy_ui.end.getTime()) {
overlay.hide();
if (overlay.data('isdraggable'))
overlay.draggable('disable');
}
else {
var table = $('#schedule-freebusy-times'),
width = 0,
pos = { top:table.children('thead').height(), left:0 },
eventstart = date2unixtime(clone_date(me.selected_event.start, me.selected_event.allDay?1:0)),
eventend = date2unixtime(clone_date(me.selected_event.end, me.selected_event.allDay?2:0)) - 60,
slotstart = date2unixtime(freebusy_ui.start),
slotsize = freebusy_ui.interval * 60,
slotend, fraction, $cell;
// iterate through slots to determine position and size of the overlay
table.children('thead').find('td').each(function(i, cell){
slotend = slotstart + slotsize - 1;
// event starts in this slot: compute left
if (eventstart >= slotstart && eventstart <= slotend) {
fraction = 1 - (slotend - eventstart) / slotsize;
pos.left = Math.round(cell.offsetLeft + cell.offsetWidth * fraction);
}
// event ends in this slot: compute width
if (eventend >= slotstart && eventend <= slotend) {
fraction = 1 - (slotend - eventend) / slotsize;
width = Math.round(cell.offsetLeft + cell.offsetWidth * fraction) - pos.left;
}
slotstart = slotstart + slotsize;
});
if (!width)
width = table.width() - pos.left;
// overlay is visible
if (width > 0) {
overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show();
// configure draggable
if (!overlay.data('isdraggable')) {
overlay.draggable({
axis: 'x',
scroll: true,
stop: function(e, ui){
// convert pixels to time
var px = ui.position.left;
var range_p = $('#schedule-freebusy-times').width();
var range_t = freebusy_ui.end.getTime() - freebusy_ui.start.getTime();
var newstart = new Date(freebusy_ui.start.getTime() + px * (range_t / range_p));
newstart.setSeconds(0); newstart.setMilliseconds(0);
// snap to day boundaries
if (me.selected_event.allDay) {
if (newstart.getHours() >= 12) // snap to next day
newstart.setTime(newstart.getTime() + DAY_MS);
newstart.setMinutes(0);
newstart.setHours(0);
}
else {
// round to 5 minutes
var round = newstart.getMinutes() % 5;
if (round > 2.5) newstart.setTime(newstart.getTime() + (5 - round) * 60000);
else if (round > 0) newstart.setTime(newstart.getTime() - round * 60000);
}
// update event times and display
update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000));
if (me.selected_event.allDay)
render_freebusy_overlay();
}
}).data('isdraggable', true);
}
else
overlay.draggable('enable');
}
else
overlay.draggable('disable').hide();
}
};
// fetch free-busy information for each attendee from server
var load_freebusy_data = function(from, interval)
{
var start = new Date(from.getTime() - DAY_MS * 2); // start 2 days before event
fix_date(start);
var end = new Date(start.getTime() + DAY_MS * Math.max(14, freebusy_ui.numdays + 7)); // load min. 14 days
freebusy_ui.numrequired = 0;
freebusy_data.all = [];
freebusy_data.required = [];
// load free-busy information for every attendee
var domid, email;
for (var i=0; i < event_attendees.length; i++) {
if ((email = event_attendees[i].email)) {
domid = String(email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).addClass('loading');
freebusy_ui.loading++;
$.ajax({
type: 'GET',
dataType: 'json',
url: rcmail.url('freebusy-times'),
data: { email:email, start:date2servertime(clone_date(start, 1)), end:date2servertime(clone_date(end, 2)), interval:interval, _remote:1 },
success: function(data) {
freebusy_ui.loading--;
// find attendee
var attendee = null;
for (var i=0; i < event_attendees.length; i++) {
if (freebusy_ui.attendees[i].email == data.email) {
attendee = freebusy_ui.attendees[i];
break;
}
}
// copy data to member var
var ts, req = attendee.role != 'OPT-PARTICIPANT';
freebusy_data.start = parseISO8601(data.start);
freebusy_data[data.email] = {};
for (var i=0; i < data.slots.length; i++) {
ts = data.times[i] + '';
freebusy_data[data.email][ts] = data.slots[i];
// set totals
if (!freebusy_data.required[ts])
freebusy_data.required[ts] = [0,0,0,0];
if (req)
freebusy_data.required[ts][data.slots[i]]++;
if (!freebusy_data.all[ts])
freebusy_data.all[ts] = [0,0,0,0];
freebusy_data.all[ts][data.slots[i]]++;
}
freebusy_data.end = parseISO8601(data.end);
freebusy_data.interval = data.interval;
// hide loading indicator
var domid = String(data.email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).removeClass('loading');
// update display
update_freebusy_display(data.email);
}
});
// count required attendees
if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT')
freebusy_ui.numrequired++;
}
}
};
// re-calculate total status after role change
var compute_freebusy_totals = function()
{
freebusy_ui.numrequired = 0;
freebusy_data.all = [];
freebusy_data.required = [];
var email, req, status;
for (var i=0; i < event_attendees.length; i++) {
if (!(email = event_attendees[i].email))
continue;
req = freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT';
if (req)
freebusy_ui.numrequired++;
for (var ts in freebusy_data[email]) {
if (!freebusy_data.required[ts])
freebusy_data.required[ts] = [0,0,0,0];
if (!freebusy_data.all[ts])
freebusy_data.all[ts] = [0,0,0,0];
status = freebusy_data[email][ts];
freebusy_data.all[ts][status]++;
if (req)
freebusy_data.required[ts][status]++;
}
}
};
// update free-busy grid with status loaded from server
var update_freebusy_display = function(email)
{
var status_classes = ['unknown','free','busy','tentative','out-of-office'];
var domid = String(email).replace(rcmail.identifier_expr, '');
var row = $('#fbrow' + domid);
var rowall = $('#fbrowall').children();
var dateonly = freebusy_ui.interval > 60,
t, ts = date2timestring(freebusy_ui.start, dateonly),
curdate = new Date(),
fbdata = freebusy_data[email];
if (fbdata && fbdata[ts] !== undefined && row.length) {
t = freebusy_ui.start.getTime();
row.children().each(function(i, cell){
curdate.setTime(t);
ts = date2timestring(curdate, dateonly);
cell.className = cell.className.replace('unknown', fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown');
// also update total row if all data was loaded
if (freebusy_ui.loading == 0 && freebusy_data.all[ts] && (cell = rowall.get(i))) {
var workinghours = cell.className.indexOf('workinghours') >= 0;
var all_status = freebusy_data.all[ts][2] ? 'busy' : 'unknown';
req_status = freebusy_data.required[ts][2] ? 'busy' : 'free';
for (var j=1; j < status_classes.length; j++) {
if (freebusy_ui.numrequired && freebusy_data.required[ts][j] >= freebusy_ui.numrequired)
req_status = status_classes[j];
if (freebusy_data.all[ts][j] == event_attendees.length)
all_status = status_classes[j];
}
cell.className = (workinghours ? 'workinghours ' : 'offhours ') + req_status + ' all-' + all_status;
}
t += freebusy_ui.interval * 60000;
});
}
};
// write changed event date/times back to form fields
var update_freebusy_dates = function(start, end)
{
// fix all-day evebt times
if (me.selected_event.allDay) {
var numdays = Math.floor((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / DAY_MS);
start.setHours(12);
start.setMinutes(0);
end.setTime(start.getTime() + numdays * DAY_MS);
end.setHours(13);
end.setMinutes(0);
}
me.selected_event.start = start;
me.selected_event.end = end;
freebusy_ui.startdate.val($.fullCalendar.formatDate(start, settings['date_format']));
freebusy_ui.starttime.val($.fullCalendar.formatDate(start, settings['time_format']));
freebusy_ui.enddate.val($.fullCalendar.formatDate(end, settings['date_format']));
freebusy_ui.endtime.val($.fullCalendar.formatDate(end, settings['time_format']));
freebusy_ui.needsupdate = true;
};
// attempt to find a time slot where all attemdees are available
var freebusy_find_slot = function(dir)
{
// exit if free-busy data isn't available yet
if (!freebusy_data || !freebusy_data.start)
return false;
var event = me.selected_event,
eventstart = clone_date(event.start, event.allDay ? 1 : 0).getTime(), // calculate with integers
eventend = clone_date(event.end, event.allDay ? 2 : 0).getTime(),
duration = eventend - eventstart - (event.allDay ? HOUR_MS : 0), /* make sure we don't cross day borders on DST change */
sinterval = freebusy_data.interval * 60000,
intvlslots = 1,
numslots = Math.ceil(duration / sinterval),
checkdate, slotend, email, ts, slot, slotdate = new Date();
// shift event times to next possible slot
eventstart += sinterval * intvlslots * dir;
eventend += sinterval * intvlslots * dir;
// iterate through free-busy slots and find candidates
var candidatecount = 0, candidatestart = candidateend = success = false;
for (slot = dir > 0 ? freebusy_data.start.getTime() : freebusy_data.end.getTime() - sinterval;
(dir > 0 && slot < freebusy_data.end.getTime()) || (dir < 0 && slot >= freebusy_data.start.getTime());
slot += sinterval * dir) {
slotdate.setTime(slot);
// fix slot if just crossed a DST change
if (event.allDay) {
fix_date(slotdate);
slot = slotdate.getTime();
}
slotend = slot + sinterval;
if ((dir > 0 && slotend <= eventstart) || (dir < 0 && slot >= eventend)) // skip
continue;
// respect workingours setting
if (freebusy_ui.workinhoursonly) {
if (is_weekend(slotdate) || (freebusy_data.interval <= 60 && !is_workinghour(slotdate))) { // skip off-hours
candidatestart = candidateend = false;
candidatecount = 0;
continue;
}
}
if (!candidatestart)
candidatestart = slot;
// check freebusy data for all attendees
ts = date2timestring(slotdate, freebusy_data.interval > 60);
for (var i=0; i < event_attendees.length; i++) {
if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT' && (email = freebusy_ui.attendees[i].email) && freebusy_data[email] && freebusy_data[email][ts] > 1) {
candidatestart = candidateend = false;
break;
}
}
// occupied slot
if (!candidatestart) {
slot += Math.max(0, intvlslots - candidatecount - 1) * sinterval * dir;
candidatecount = 0;
continue;
}
// set candidate end to slot end time
candidatecount++;
if (dir < 0 && !candidateend)
candidateend = slotend;
// if candidate is big enough, this is it!
if (candidatecount == numslots) {
if (dir > 0) {
event.start.setTime(candidatestart);
event.end.setTime(candidatestart + duration);
}
else {
event.end.setTime(candidateend);
event.start.setTime(candidateend - duration);
}
success = true;
break;
}
}
// update event date/time display
if (success) {
update_freebusy_dates(event.start, event.end);
// move freebusy grid if necessary
var offset = Math.ceil((event.start.getTime() - freebusy_ui.end.getTime()) / DAY_MS);
if (event.start.getTime() >= freebusy_ui.end.getTime())
render_freebusy_grid(Math.max(1, offset));
else if (event.end.getTime() <= freebusy_ui.start.getTime())
render_freebusy_grid(Math.min(-1, offset));
else
render_freebusy_overlay();
var now = new Date();
$('#shedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime()));
// speak new selection
rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice');
}
else {
alert(rcmail.gettext('noslotfound','calendar'));
}
};
// update event properties and attendees availability if event times have changed
var event_times_changed = function()
{
if (me.selected_event) {
var allday = $('#edit-allday').get(0);
me.selected_event.allDay = allday.checked;
me.selected_event.start = parse_datetime(allday.checked ? '12:00' : $('#edit-starttime').val(), $('#edit-startdate').val());
me.selected_event.end = parse_datetime(allday.checked ? '13:00' : $('#edit-endtime').val(), $('#edit-enddate').val());
if (event_attendees)
freebusy_ui.needsupdate = true;
$('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000));
}
};
// add the given list of participants
var add_attendees = function(names, params)
{
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
// parse name/email pairs
var item, email, name, success = false;
for (var i=0; i < names.length; i++) {
email = name = '';
item = $.trim(names[i]);
if (!item.length) {
continue;
} // address in brackets without name (do nothing)
else if (item.match(/^<[^@]+@[^>]+>$/)) {
email = item.replace(/[<>]/g, '');
} // address without brackets and without name (add brackets)
else if (rcube_check_email(item)) {
email = item;
} // address with name
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
email = RegExp.$1;
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
}
if (email) {
add_attendee($.extend({ email:email, name:name }, params));
success = true;
}
else {
alert(rcmail.gettext('noemailwarning'));
}
}
return success;
};
// add the given attendee to the list
var add_attendee = function(data, readonly, before)
{
if (!me.selected_event)
return false;
// check for dupes...
var exists = false;
$.each(event_attendees, function(i, v){ exists |= (v.email == data.email); });
if (exists)
return false;
var calendar = me.selected_event && me.calendars[me.selected_event.calendar] ? me.calendars[me.selected_event.calendar] : me.calendars[me.selected_calendar];
var dispname = Q(data.name || data.email);
if (data.email)
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
// role selection
var organizer = data.role == 'ORGANIZER';
var opts = {};
if (organizer)
opts.ORGANIZER = rcmail.gettext('calendar.roleorganizer');
opts['REQ-PARTICIPANT'] = rcmail.gettext('calendar.rolerequired');
opts['OPT-PARTICIPANT'] = rcmail.gettext('calendar.roleoptional');
opts['NON-PARTICIPANT'] = rcmail.gettext('calendar.rolenonparticipant');
if (data.cutype != 'RESOURCE')
opts['CHAIR'] = rcmail.gettext('calendar.rolechair');
if (organizer && !readonly)
dispname = rcmail.env['identities-selector'];
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','calendar') + '">';
for (var r in opts)
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
select += '</select>';
// availability
var avail = data.email ? 'loading' : 'unknown';
// delete icon
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
var tooltip = '', status = (data.status || '').toLowerCase(),
status_label = rcmail.gettext('status' + status, 'libcalendaring');
// send invitation checkbox
var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('calendar.sendinvitations')) + '" '
+ (!data.noreply && settings.itip_notify & 1 ? 'checked="checked" ' : '') + '/>';
if (data['delegated-to'])
tooltip = rcmail.gettext('delegatedto', 'calendar') + data['delegated-to'];
else if (data['delegated-from'])
tooltip = rcmail.gettext('delegatedfrom', 'calendar') + data['delegated-from'];
else if (status)
tooltip = status_label;
// add expand button for groups
if (data.cutype == 'GROUP') {
dispname += ' <a href="#expand" data-email="' + Q(data.email) + '" class="iconbutton add expandlink" title="' + rcmail.gettext('expandattendeegroup','libcalendaring') + '">' +
rcmail.gettext('expandattendeegroup','libcalendaring') + '</a>';
}
var img_src = rcmail.assets_path('program/resources/blank.gif');
var html = '<td class="role">' + select + '</td>' +
'<td class="name"><span class="attendee-name">' + dispname + '</span></td>' +
'<td class="availability"><img src="' + img_src + '" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
'<td class="confirmstate"><span class="' + status + '" title="' + Q(tooltip) + '">' + Q(status ? status_label : '') + '</span></td>' +
(data.cutype != 'RESOURCE' ? '<td class="invite">' + (organizer || readonly || !invbox ? '' : invbox) + '</td>' : '') +
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
var table = rcmail.env.calendar_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
var tr = $('<tr>')
.addClass(String(data.role).toLowerCase())
.html(html);
if (before)
tr.insertBefore(before)
else
tr.appendTo(table);
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
tr.find('a.mailtolink').click(event_attendee_click);
tr.find('a.expandlink').click(data, function(e) { me.expand_attendee_group(e, add_attendee, remove_attendee); return false; });
tr.find('input.edit-attendee-reply').click(function() {
var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
$('#eventedit .attendees-commentbox')[enabled ? 'show' : 'hide']();
});
// select organizer identity
if (data.identity_id)
$('#edit-identities-list').val(data.identity_id);
// check free-busy status
if (avail == 'loading') {
check_freebusy_status(tr.find('img.availabilityicon'), data.email, me.selected_event);
}
event_attendees.push(data);
return true;
};
// iterate over all attendees and update their free-busy status display
var update_freebusy_status = function(event)
{
attendees_list.find('img.availabilityicon').each(function(i,v) {
var email, icon = $(this);
if (email = icon.attr('data-email'))
check_freebusy_status(icon, email, event);
});
freebusy_ui.needsupdate = false;
};
// load free-busy status from server and update icon accordingly
var check_freebusy_status = function(icon, email, event)
{
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { freebusy:false };
if (!calendar.freebusy) {
$(icon).attr('class', 'availabilityicon unknown');
return;
}
icon = $(icon).attr('class', 'availabilityicon loading');
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('freebusy-status'),
data: { email:email, start:date2servertime(clone_date(event.start, event.allDay?1:0)), end:date2servertime(clone_date(event.end, event.allDay?2:0)), _remote: 1 },
success: function(status){
var avail = String(status).toLowerCase();
icon.removeClass('loading').addClass(avail).attr('alt', rcmail.gettext('avail' + avail, 'calendar'));
},
error: function(){
icon.removeClass('loading').addClass('unknown').attr('alt', rcmail.gettext('availunknown', 'calendar'));
}
});
};
// remove an attendee from the list
var remove_attendee = function(elem, id)
{
$(elem).closest('tr').remove();
event_attendees = $.grep(event_attendees, function(data){ return (data.name != id && data.email != id) });
};
// open a dialog to display detailed free-busy information and to find free slots
var event_resources_dialog = function(search)
{
var $dialog = $('#eventresourcesdialog');
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('addresource', 'calendar')] = function() {
rcmail.command('add-resource');
};
buttons[rcmail.gettext('close')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('findresources', 'calendar'),
open: function() {
rcmail.ksearch_blur();
$dialog.attr('aria-hidden', 'false');
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
},
resize: function(e) {
var container = $(rcmail.gui_objects.resourceinfocalendar);
container.fullCalendar('option', 'height', container.height() + 4);
},
buttons: buttons,
width: 900,
height: 500
}).show();
// define add-button as main action
$('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().addClass('mainaction').attr('id', 'rcmbtncalresadd');
me.dialog_resize($dialog.get(0), 540, Math.min(1000, $(window).width() - 50));
// set search query
$('#resourcesearchbox').val(search || '');
// initialize the treelist widget
if (!resources_treelist) {
resources_treelist = new rcube_treelist_widget(rcmail.gui_objects.resourceslist, {
id_prefix: 'rcres',
id_encode: rcmail.html_identifier_encode,
id_decode: rcmail.html_identifier_decode,
selectable: true,
save_state: true
});
resources_treelist.addEventListener('select', function(node) {
if (resources_data[node.id]) {
resource_showinfo(resources_data[node.id]);
rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible') ? true : false);
}
else {
rcmail.enable_command('add-resource', false);
$(rcmail.gui_objects.resourceinfo).hide();
$(rcmail.gui_objects.resourceownerinfo).hide();
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
}
});
// fetch (all) resource data from server
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
rcmail.http_request('resources-list', {}, me.loading_lock);
// register button
rcmail.register_button('add-resource', 'rcmbtncalresadd', 'uibutton');
// initialize resource calendar display
var resource_cal = $(rcmail.gui_objects.resourceinfocalendar);
resource_cal.fullCalendar($.extend({}, fullcalendar_defaults, {
header: { left: '', center: '', right: '' },
height: resource_cal.height() + 4,
defaultView: 'agendaWeek',
eventSources: [],
slotMinutes: 60,
allDaySlot: false,
eventRender: function(event, element, view) {
var title = rcmail.get_label(event.status, 'calendar');
element.addClass('status-' + event.status);
element.find('.fc-event-head').hide();
element.find('.fc-event-title').text(title);
element.attr('aria-label', me.event_date_text(event, true) + ': ' + title);
}
}));
$('#resource-calendar-prev').click(function(){
resource_cal.fullCalendar('prev');
return false;
});
$('#resource-calendar-next').click(function(){
resource_cal.fullCalendar('next');
return false;
});
}
else if (search) {
resource_search();
}
else {
resource_render_list(resources_index);
}
if (me.selected_event && me.selected_event.start) {
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('gotoDate', me.selected_event.start);
}
};
// render the resource details UI box
var resource_showinfo = function(resource)
{
// inline function to render a resource attribute
function render_attrib(value) {
if (typeof value == 'boolean') {
return value ? rcmail.get_label('yes') : rcmail.get_label('no');
}
return value;
}
if (rcmail.gui_objects.resourceinfo) {
var tr, table = $(rcmail.gui_objects.resourceinfo).show().find('tbody').html(''),
attribs = $.extend({ name:resource.name }, resource.attributes||{})
attribs.description = resource.description;
for (var k in attribs) {
if (typeof attribs[k] == 'undefined')
continue;
table.append($('<tr>').addClass(k)
.append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
.append('<td class="value">' + text2html(render_attrib(attribs[k])) + '</td>')
);
}
$(rcmail.gui_objects.resourceownerinfo).hide();
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
if (resource.owner) {
// display cached data
if (resource_owners[resource.owner]) {
resource_owner_load(resource_owners[resource.owner]);
}
else {
// fetch owner data from server
me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
rcmail.http_request('resources-owner', { _id: resource.owner }, me.loading_lock);
}
}
// load resource calendar
resources_events_source.url = "./?_task=calendar&_action=resources-calendar&_id="+urlencode(resource.ID);
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('addEventSource', resources_events_source);
}
};
// callback from server for resource listing
var resource_data_load = function(data)
{
var resources_tree = {};
// store data by ID
$.each(data, function(i, rec) {
resources_data[rec.ID] = rec;
// assign parent-relations
if (rec.members) {
$.each(rec.members, function(j, m){
resources_tree[m] = rec.ID;
});
}
});
// walk the parent-child tree to determine the depth of each node
$.each(data, function(i, rec) {
rec._depth = 0;
if (resources_tree[rec.ID])
rec.parent_id = resources_tree[rec.ID];
var parent_id = resources_tree[rec.ID];
while (parent_id) {
rec._depth++;
parent_id = resources_tree[parent_id];
}
});
// sort by depth, collection and name
data.sort(function(a,b) {
var j = a._type == 'collection' ? 1 : 0,
k = b._type == 'collection' ? 1 : 0,
d = a._depth - b._depth;
if (!d) d = (k - j);
if (!d) d = b.name < a.name ? 1 : -1;
return d;
});
$.each(data, function(i, rec) {
resources_index.push(rec.ID);
});
// apply search filter...
if ($('#resourcesearchbox').val() != '')
resource_search();
else // ...or render full list
resource_render_list(resources_index);
rcmail.set_busy(false, null, me.loading_lock);
};
// renders the given list of resource records into the treelist
var resource_render_list = function(index) {
var rec, link;
resources_treelist.reset();
$.each(index, function(i, dn) {
if (rec = resources_data[dn]) {
link = $('<a>').attr('href', '#')
.attr('rel', rec.ID)
.html(Q(rec.name));
resources_treelist.insert({ id:rec.ID, html:link, classes:[rec._type], collapsed:true }, rec.parent_id, false);
}
});
};
// callback from server for owner information display
var resource_owner_load = function(data)
{
if (data) {
// cache this!
resource_owners[data.ID] = data;
var table = $(rcmail.gui_objects.resourceownerinfo).find('tbody').html('');
for (var k in data) {
if (k == 'event' || k == 'ID')
continue;
table.append($('<tr>').addClass(k)
.append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
.append('<td class="value">' + text2html(data[k]) + '</td>')
);
}
table.parent().show();
}
}
// quick-filter the loaded resource data
var resource_search = function()
{
var dn, rec, dataset = [],
q = $('#resourcesearchbox').val().toLowerCase();
if (q.length && resources_data) {
// search by iterating over all resource records
for (dn in resources_data) {
rec = resources_data[dn];
if ((rec.name && String(rec.name).toLowerCase().indexOf(q) >= 0)
|| (rec.email && String(rec.email).toLowerCase().indexOf(q) >= 0)
|| (rec.description && String(rec.description).toLowerCase().indexOf(q) >= 0)
) {
dataset.push(rec.ID);
}
}
resource_render_list(dataset);
// select single match
if (dataset.length == 1) {
resources_treelist.select(dataset[0]);
}
}
else {
$('#resourcesearchbox').val('');
}
};
//
var reset_resource_search = function()
{
$('#resourcesearchbox').val('').focus();
resource_render_list(resources_index);
};
//
var add_resource2event = function()
{
var resource = resources_data[resources_treelist.get_selection()];
if (resource) {
if (add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource)))
rcmail.display_message(rcmail.get_label('resourceadded', 'calendar'), 'confirmation');
}
}
// when the user accepts or declines an event invitation
var event_rsvp = function(response, delegate, replymode)
{
var btn;
if (typeof response == 'object') {
btn = $(response);
response = btn.attr('rel')
}
else {
btn = $('#event-rsvp input.button[rel='+response+']');
}
// show menu to select rsvp reply mode (current or all)
if (me.selected_event && me.selected_event.recurrence && !replymode) {
rcube_libcalendaring.itip_rsvp_recurring(btn, function(resp, mode) {
event_rsvp(resp, null, mode);
});
return;
}
if (me.selected_event && me.selected_event.attendees && response) {
// bring up delegation dialog
if (response == 'delegated' && !delegate) {
rcube_libcalendaring.itip_delegate_dialog(function(data) {
data.rsvp = data.rsvp ? 1 : '';
event_rsvp('delegated', data, replymode);
});
return;
}
// update attendee status
attendees = [];
for (var data, i=0; i < me.selected_event.attendees.length; i++) {
data = me.selected_event.attendees[i];
if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
data.status = response.toUpperCase();
data.rsvp = 0; // unset RSVP flag
if (data.status == 'DELEGATED') {
data['delegated-to'] = delegate.to;
data.rsvp = delegate.rsvp
}
else {
if (data['delegated-to']) {
delete data['delegated-to'];
if (data.role == 'NON-PARTICIPANT' && data.status != 'DECLINED')
data.role = 'REQ-PARTICIPANT';
}
}
attendees.push(i)
}
else if (response != 'DELEGATED' && data['delegated-from'] &&
settings.identity.emails.indexOf(';'+String(data['delegated-from']).toLowerCase()) >= 0) {
delete data['delegated-from'];
}
// set free_busy status to transparent if declined (#4425)
if (data.status == 'DECLINED' || data.role == 'NON-PARTICIPANT') {
me.selected_event.free_busy = 'free';
}
else {
me.selected_event.free_busy = 'busy';
}
}
// submit status change to server
var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})),
noreply = $('#noreply-event-rsvp:checked').length ? 1 : 0;
// import event from mail (temporary iTip event)
if (submit_data._mbox && submit_data._uid) {
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('mailimportitip', {
_mbox: submit_data._mbox,
_uid: submit_data._uid,
_part: submit_data._part,
_status: response,
_to: (delegate ? delegate.to : null),
_rsvp: (delegate && delegate.rsvp) ? 1 : 0,
_noreply: noreply,
_comment: submit_data.comment,
_instance: submit_data._instance,
_savemode: submit_data._savemode
});
}
else if (settings.invitation_calendars) {
update_event('rsvp', submit_data, { status:response, noreply:noreply, attendees:attendees });
}
else {
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response, attendees:attendees, noreply:noreply });
}
event_show_dialog(me.selected_event);
}
};
// add the given date to the RDATE list
var add_rdate = function(date)
{
var li = $('<li>')
.attr('data-value', date2servertime(date))
.html('<span>' + Q($.fullCalendar.formatDate(date, settings['date_format'])) + '</span>')
.appendTo('#edit-recurrence-rdates');
$('<a>').attr('href', '#del')
.addClass('iconbutton delete')
.html(rcmail.get_label('delete', 'calendar'))
.attr('title', rcmail.get_label('delete', 'calendar'))
.appendTo(li);
};
// re-sort the list items by their 'data-value' attribute
var sort_rdates = function()
{
var mylist = $('#edit-recurrence-rdates'),
listitems = mylist.children('li').get();
listitems.sort(function(a, b) {
var compA = $(a).attr('data-value');
var compB = $(b).attr('data-value');
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
})
$.each(listitems, function(idx, item) { mylist.append(item); });
}
// remove the link reference matching the given uri
function remove_link(elem)
{
var $elem = $(elem), uri = $elem.attr('data-uri');
me.selected_event.links = $.grep(me.selected_event.links, function(link) { return link.uri != uri; });
// remove UI list item
$elem.hide().closest('li').addClass('deleted');
}
// post the given event data to server
var update_event = function(action, data, add)
{
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('calendar/event', $.extend({ action:action, e:data }, (add || {})));
// render event temporarily into the calendar
if ((data.start && data.end) || data.id) {
var event = data.id ? $.extend(fc.fullCalendar('clientEvents', function(e){ return e.id == data.id; })[0], data) : data;
if (data.start)
event.start = data.start;
if (data.end)
event.end = data.end;
if (data.allday !== undefined)
event.allDay = data.allday;
event.editable = false;
event.temp = true;
event.className = 'fc-event-cal-'+data.calendar+' fc-event-temp';
fc.fullCalendar(data.id ? 'updateEvent' : 'renderEvent', event);
// mark all recurring instances as temp
if (event.recurrence || event.recurrence_id) {
var base_id = event.recurrence_id ? event.recurrence_id : String(event.id).replace(/-\d+(T\d{6})?$/, '');
$.each(fc.fullCalendar('clientEvents', function(e){ return e.id == base_id || e.recurrence_id == base_id; }), function(i,ev) {
ev.temp = true;
ev.editable = false;
event.className += ' fc-event-temp';
fc.fullCalendar('updateEvent', ev);
});
}
}
};
// mouse-click handler to check if the show dialog is still open and prevent default action
var dialog_check = function(e)
{
var showd = $("#eventshow");
if (showd.is(':visible') && !$(e.target).closest('.ui-dialog').length && !$(e.target).closest('.popupmenu').length) {
showd.dialog('close');
e.stopImmediatePropagation();
ignore_click = true;
return false;
}
else if (ignore_click) {
window.setTimeout(function(){ ignore_click = false; }, 20);
return false;
}
return true;
};
// display confirm dialog when modifying/deleting an event
var update_event_confirm = function(action, event, data)
{
// Allow other plugins to do actions here
// E.g. when you move/resize the event init wasn't called
// but we need it as some plugins may modify user identities
// we depend on here (kolab_delegation)
rcmail.triggerEvent('calendar-event-init', {o: event});
if (!data) data = event;
var decline = false, notify = false, html = '', cal = me.calendars[event.calendar],
_has_attendees = has_attendees(event), _is_organizer = is_organizer(event);
// event has attendees, ask whether to notify them
if (_has_attendees) {
var checked = (settings.itip_notify & 1 ? ' checked="checked"' : '');
if (_is_organizer) {
notify = true;
if (settings.itip_notify & 2) {
html += '<div class="message">' +
'<label><input class="confirm-attendees-donotify" type="checkbox"' + checked + ' value="1" name="notify" />&nbsp;' +
rcmail.gettext((action == 'remove' ? 'sendcancellation' : 'sendnotifications'), 'calendar') +
'</label></div>';
}
else {
data._notify = settings.itip_notify;
}
}
else if (action == 'remove' && is_attendee(event)) {
decline = true;
checked = event.status != 'CANCELLED' ? checked : '';
html += '<div class="message">' +
'<label><input class="confirm-attendees-decline" type="checkbox"' + checked + ' value="1" name="decline" />&nbsp;' +
rcmail.gettext('itipdeclineevent', 'calendar') +
'</label></div>';
}
else {
html += '<div class="message">' + rcmail.gettext('localchangeswarning', 'calendar') + '</div>';
}
}
// recurring event: user needs to select the savemode
if (event.recurrence) {
var future_disabled = '', message_label = (action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning');
// disable the 'future' savemode if I'm an attendee
// reason: no calendaring system supports the thisandfuture range parameter in iTip REPLY
if (action == 'remove' && _has_attendees && !_is_organizer && is_attendee(event)) {
future_disabled = ' disabled';
}
html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
rcmail.gettext(message_label, 'calendar') + '</div>' +
'<div class="savemode">' +
'<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
'<a href="#future" class="button' + future_disabled + '">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
'<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
(action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
'</div>';
}
// show dialog
if (html) {
var $dialog = $('<div>').html(html);
$dialog.find('a.button').button().filter(':not(.disabled)').click(function(e) {
data._savemode = String(this.href).replace(/.+#/, '');
data._notify = settings.itip_notify;
// open event edit dialog when saving as new
if (data._savemode == 'new') {
event._savemode = 'new';
event_edit_dialog('edit', event);
fc.fullCalendar('refetchEvents');
}
else {
if ($dialog.find('input.confirm-attendees-donotify').length)
data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
if (decline) {
data._decline = $dialog.find('input.confirm-attendees-decline:checked').length;
data._notify = 0;
}
update_event(action, data);
}
$dialog.dialog("close");
return false;
});
var buttons = [];
if (!event.recurrence) {
buttons.push({
text: rcmail.gettext((action == 'remove' ? 'delete' : 'save'), 'calendar'),
click: function() {
data._notify = notify && $dialog.find('input.confirm-attendees-donotify:checked').length ? 1 : 0;
data._decline = decline && $dialog.find('input.confirm-attendees-decline:checked').length ? 1 : 0;
update_event(action, data);
$(this).dialog("close");
}
});
}
buttons.push({
text: rcmail.gettext('cancel', 'calendar'),
click: function() {
$(this).dialog("close");
}
});
$dialog.dialog({
modal: true,
width: 460,
dialogClass: 'warning',
title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
buttons: buttons,
open: function() {
setTimeout(function(){
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
}, 5);
},
close: function(){
$dialog.dialog("destroy").remove();
if (!rcmail.busy)
fc.fullCalendar('refetchEvents');
}
}).addClass('event-update-confirm').show();
return false;
}
// show regular confirm box when deleting
else if (action == 'remove' && !cal.undelete) {
if (!confirm(rcmail.gettext('deleteventconfirm', 'calendar')))
return false;
}
// do update
update_event(action, data);
return true;
};
var update_agenda_toolbar = function()
{
$('#agenda-listrange').val(fc.fullCalendar('option', 'listRange'));
$('#agenda-listsections').val(fc.fullCalendar('option', 'listSections'));
}
/*** public methods ***/
/**
* Remove saving lock and free the UI for new input
*/
this.unlock_saving = function()
{
if (me.saving_lock)
rcmail.set_busy(false, null, me.saving_lock);
};
// opens calendar day-view in a popup
this.fisheye_view = function(date)
{
$('#fish-eye-view:ui-dialog').dialog('close');
// create list of active event sources
var src, cals = {}, sources = [];
for (var id in this.calendars) {
src = $.extend({}, this.calendars[id]);
src.editable = false;
src.url = null;
src.events = [];
if (src.active) {
cals[id] = src;
sources.push(src);
}
}
// copy events already loaded
var events = fc.fullCalendar('clientEvents');
for (var event, i=0; i< events.length; i++) {
event = events[i];
if (event.source && (src = cals[event.source.id])) {
src.events.push(event);
}
}
var h = $(window).height() - 50;
var dialog = $('<div>')
.attr('id', 'fish-eye-view')
.dialog({
modal: true,
width: 680,
height: h,
title: $.fullCalendar.formatDate(date, 'dddd ' + settings['date_long']),
close: function(){
dialog.dialog("destroy");
me.fisheye_date = null;
}
})
.fullCalendar($.extend({}, fullcalendar_defaults, {
defaultView: 'agendaDay',
header: { left: '', center: '', right: '' },
height: h - 50,
date: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
eventSources: sources
}));
this.fisheye_date = date;
};
// opens the given calendar in a popup dialog
this.quickview = function(id, shift)
{
var src, in_quickview = false;
$.each(this.quickview_sources, function(i,cal) {
if (cal.id == id) {
in_quickview = true;
src = cal;
}
});
// remove source from quickview
if (in_quickview && shift) {
this.quickview_sources = $.grep(this.quickview_sources, function(src) { return src.id != id; });
}
else {
if (!shift) {
// remove all current quickview event sources
if (this.quickview_active) {
fc.fullCalendar('removeEventSources');
}
this.quickview_sources = [];
// uncheck all active quickview icons
calendars_list.container.find('div.focusview')
.add('#calendars .searchresults div.focusview')
.removeClass('focusview')
.find('a.quickview').attr('aria-checked', 'false');
}
if (!in_quickview) {
// clone and modify calendar properties
src = $.extend({}, this.calendars[id]);
src.url += '&_quickview=1';
this.quickview_sources.push(src);
}
}
// disable quickview
if (this.quickview_active && !this.quickview_sources.length) {
// register regular calendar event sources
$.each(this.calendars, function(k, cal) {
if (cal.active)
fc.fullCalendar('addEventSource', cal);
});
this.quickview_active = false;
$('body').removeClass('quickview-active');
// uncheck all active quickview icons
calendars_list.container.find('div.focusview')
.add('#calendars .searchresults div.focusview')
.removeClass('focusview')
.find('a.quickview').attr('aria-checked', 'false');
}
// activate quickview
else if (!this.quickview_active) {
// remove regular calendar event sources
fc.fullCalendar('removeEventSources');
// register quickview event sources
$.each(this.quickview_sources, function(i, src) {
fc.fullCalendar('addEventSource', src);
});
this.quickview_active = true;
$('body').addClass('quickview-active');
}
// update quickview sources
else if (in_quickview) {
fc.fullCalendar('removeEventSource', src);
}
else if (src) {
fc.fullCalendar('addEventSource', src);
}
// activate quickview icon
if (this.quickview_active) {
$(calendars_list.get_item(id)).find('.calendar').first()
.add('#calendars .searchresults .cal-' + id)
[in_quickview ? 'removeClass' : 'addClass']('focusview')
.find('a.quickview').attr('aria-checked', in_quickview ? 'false' : 'true');
}
};
// disable quickview mode
function reset_quickview()
{
// remove all current quickview event sources
if (me.quickview_active) {
fc.fullCalendar('removeEventSources');
me.quickview_sources = [];
}
// register regular calendar event sources
$.each(me.calendars, function(k, cal) {
if (cal.active)
fc.fullCalendar('addEventSource', cal);
});
// uncheck all active quickview icons
calendars_list.container.find('div.focusview')
.add('#calendars .searchresults div.focusview')
.removeClass('focusview')
.find('a.quickview').attr('aria-checked', 'false');
me.quickview_active = false;
$('body').removeClass('quickview-active');
};
//public method to show the print dialog.
this.print_calendars = function(view)
{
if (!view) view = fc.fullCalendar('getView').name;
var date = fc.fullCalendar('getDate') || new Date();
var range = fc.fullCalendar('option', 'listRange');
var sections = fc.fullCalendar('option', 'listSections');
rcmail.open_window(rcmail.url('print', { view: view, date: date2unixtime(date), range: range, sections: sections, search: this.search_query }), true, true);
};
// public method to bring up the new event dialog
this.add_event = function(templ) {
if (this.selected_calendar) {
var now = new Date();
var date = fc.fullCalendar('getDate');
if (typeof date != 'Date')
date = now;
date.setHours(now.getHours()+1);
date.setMinutes(0);
var end = new Date(date.getTime());
end.setHours(date.getHours()+1);
event_edit_dialog('new', $.extend({ start:date, end:end, allDay:false, calendar:this.selected_calendar }, templ || {}));
}
};
// delete the given event after showing a confirmation dialog
this.delete_event = function(event) {
// show confirm dialog for recurring events, use jquery UI dialog
return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees });
};
// opens a jquery UI dialog with event properties (or empty for creating a new calendar)
this.calendar_edit_dialog = function(calendar)
{
// close show dialog first
var $dialog = $("#calendarform");
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (!calendar)
calendar = { name:'', color:'cc0000', editable:true, showalarms:true };
var form, name, color, alarms;
$dialog.html(rcmail.get_label('loading'));
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('calendar'),
data: { action:(calendar.id ? 'form-edit' : 'form-new'), c:{ id:calendar.id } },
success: function(data) {
$dialog.html(data);
// resize and reposition dialog window
form = $('#calendarpropform');
me.dialog_resize('#calendarform', form.height(), form.width());
name = $('#calendar-name').prop('disabled', !calendar.editable).val(calendar.editname || calendar.name);
color = $('#calendar-color').val(calendar.color).miniColors({ value: calendar.color, colorValues:rcmail.env.mscolors });
alarms = $('#calendar-showalarms').prop('checked', calendar.showalarms).get(0);
name.select();
}
});
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save', 'calendar')] = function() {
// form is not loaded
if (!form || !form.length)
return;
// TODO: do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidcalendarproperties', 'calendar'));
name.select();
return;
}
// post data to server
var data = form.serializeJSON();
if (data.color)
data.color = data.color.replace(/^#/, '');
if (calendar.id)
data.id = calendar.id;
if (alarms)
data.showalarms = alarms.checked ? 1 : 0;
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('calendar', { action:(calendar.id ? 'edit' : 'new'), c:data });
$dialog.dialog("close");
};
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
title: rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$dialog.html('').dialog("destroy").hide();
},
buttons: buttons,
minWidth: 400,
width: 420
}).show();
};
this.calendar_remove = function(calendar)
{
this.calendar_destroy_source(calendar.id);
rcmail.http_post('calendar', { action:'subscribe', c:{ id:calendar.id, active:0, permanent:0, recursive:1 } });
return true;
};
this.calendar_delete = function(calendar)
{
if (confirm(rcmail.gettext(calendar.children ? 'deletecalendarconfirmrecursive' : 'deletecalendarconfirm', 'calendar'))) {
rcmail.http_post('calendar', { action:'delete', c:{ id:calendar.id } });
return true;
}
return false;
};
this.calendar_refresh_source = function(id)
{
// got race-conditions fc.currentFetchID when using refetchEvents,
// so we remove and add the source instead
// fc.fullCalendar('refetchEvents', me.calendars[id]);
fc.fullCalendar('removeEventSource', me.calendars[id]);
fc.fullCalendar('addEventSource', me.calendars[id]);
};
this.calendar_destroy_source = function(id)
{
var delete_ids = [];
if (this.calendars[id]) {
// find sub-calendars
if (this.calendars[id].children) {
for (var child_id in this.calendars) {
if (String(child_id).indexOf(id) == 0)
delete_ids.push(child_id);
}
}
else {
delete_ids.push(id);
}
}
// delete all calendars in the list
for (var i=0; i < delete_ids.length; i++) {
id = delete_ids[i];
calendars_list.remove(id);
fc.fullCalendar('removeEventSource', this.calendars[id]);
$('#edit-calendar option[value="'+id+'"]').remove();
delete this.calendars[id];
}
};
// open a dialog to upload an .ics file with events to be imported
this.import_events = function(calendar)
{
// close show dialog first
var $dialog = $("#eventsimport"),
form = rcmail.gui_objects.importform;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (calendar)
$('#event-import-calendar').val(calendar.id);
var buttons = {};
buttons[rcmail.gettext('import', 'calendar')] = function() {
if (form && form.elements._data.value) {
rcmail.async_upload_form(form, 'import_events', function(e) {
rcmail.set_busy(false, null, me.saving_lock);
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
// display error message if no sophisticated response from server arrived (e.g. iframe load error)
if (me.import_succeeded === null)
rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error');
});
// display upload indicator (with extended timeout)
var timeout = rcmail.env.request_timeout;
rcmail.env.request_timeout = 600;
me.import_succeeded = null;
me.saving_lock = rcmail.set_busy(true, 'uploading');
$('.ui-dialog-buttonpane button', $dialog.parent()).button('disable');
// restore settings
rcmail.env.request_timeout = timeout;
}
};
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: false,
closeOnEscape: false,
title: rcmail.gettext('importevents', 'calendar'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
$dialog.dialog("destroy").hide();
},
buttons: buttons,
width: 520
}).show();
};
// callback from server if import succeeded
this.import_success = function(p)
{
this.import_succeeded = true;
$("#eventsimport:ui-dialog").dialog('close');
rcmail.set_busy(false, null, me.saving_lock);
rcmail.gui_objects.importform.reset();
if (p.refetch)
this.refresh(p);
};
// callback from server to report errors on import
this.import_error = function(p)
{
this.import_succeeded = false;
rcmail.set_busy(false, null, me.saving_lock);
rcmail.display_message(p.message || rcmail.get_label('importerror', 'calendar'), 'error');
}
// open a dialog to select calendars for export
this.export_events = function(calendar)
{
// close show dialog first
var $dialog = $("#eventsexport"),
form = rcmail.gui_objects.exportform;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (calendar)
$('#event-export-calendar').val(calendar.id);
$('#event-export-range').change(function(e){
var custom = $('option:selected', this).val() == 'custom',
input = $('#event-export-startdate')
input.parent()[(custom?'show':'hide')]();
if (custom)
input.select();
})
var buttons = {};
buttons[rcmail.gettext('export', 'calendar')] = function() {
if (form) {
var start = 0, range = $('#event-export-range option:selected', this).val(),
source = $('#event-export-calendar option:selected').val(),
attachmt = $('#event-export-attachments').get(0).checked;
if (range == 'custom')
start = date2unixtime(parse_datetime('00:00', $('#event-export-startdate').val()));
else if (range > 0)
start = 'today -' + range + ' months';
rcmail.goto_url('export_events', { source:source, start:start, attachments:attachmt?1:0 });
}
$dialog.dialog("close");
};
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: false,
closeOnEscape: false,
title: rcmail.gettext('exporttitle', 'calendar'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
$dialog.dialog("destroy").hide();
},
buttons: buttons,
width: 520
}).show();
};
// download the selected event as iCal
this.event_download = function(event)
{
if (event && event.id) {
rcmail.goto_url('export_events', { source:event.calendar, id:event.id, attachments:1 });
}
};
// open the message compose step with a calendar_event parameter referencing the selected event.
// the server-side plugin hook will pick that up and attach the event to the message.
this.event_sendbymail = function(event, e)
{
if (event && event.id) {
rcmail.command('compose', { _calendar_event:event._id }, e ? e.target : null, e);
}
};
// display the edit dialog, request 'new' action and pass the selected event
this.event_copy = function(event) {
if (event && event.id) {
var copy = $.extend(true, {}, event);
delete copy.id;
delete copy._id;
delete copy.created;
delete copy.changed;
delete copy.recurrence_id;
delete copy.attachments; // @TODO
$.each(copy.attendees, function (k, v) {
if (v.role != 'ORGANIZER') {
v.status = 'NEEDS-ACTION';
}
})
event_edit_dialog('new', copy);
}
};
// show URL of the given calendar in a dialog box
this.showurl = function(calendar)
{
var $dialog = $('#calendarurlbox');
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (calendar.feedurl) {
if (calendar.caldavurl) {
$('#caldavurl').val(calendar.caldavurl);
$('#calendarcaldavurl').show();
}
else {
$('#calendarcaldavurl').hide();
}
$dialog.dialog({
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('showurl', 'calendar'),
close: function() {
$dialog.dialog("destroy").hide();
},
width: 520
}).show();
$('#calfeedurl').val(calendar.feedurl).select();
}
};
// refresh the calendar view after saving event data
this.refresh = function(p)
{
var source = me.calendars[p.source];
// helper function to update the given fullcalendar view
function update_view(view, event, source) {
var existing = view.fullCalendar('clientEvents', event._id);
if (existing.length) {
$.extend(existing[0], event);
view.fullCalendar('updateEvent', existing[0]);
// remove old recurrence instances
if (event.recurrence && !event.recurrence_id)
view.fullCalendar('removeEvents', function(e){ return e._id.indexOf(event._id+'-') == 0; });
}
else {
event.source = source; // link with source
view.fullCalendar('renderEvent', event);
}
}
// remove temp events
fc.fullCalendar('removeEvents', function(e){ return e.temp; });
if (source && (p.refetch || (p.update && !source.active))) {
// activate event source if new event was added to an invisible calendar
if (this.quickview_active) {
// map source to the quickview_sources equivalent
$.each(this.quickview_sources, function(src) {
if (src.id == source.id) {
source = src;
return false;
}
});
fc.fullCalendar('refetchEvents', source, true);
}
else if (!source.active) {
source.active = true;
fc.fullCalendar('addEventSource', source);
$('#rcmlical' + source.id + ' input').prop('checked', true);
}
else
fc.fullCalendar('refetchEvents', source, true);
fetch_counts();
}
// add/update single event object
else if (source && p.update) {
var event = p.update;
event.temp = false;
event.editable = 0;
// update fish-eye view
if (this.fisheye_date)
update_view($('#fish-eye-view'), event, source);
// update main view
event.editable = source.editable;
update_view(fc, event, source);
// update the currently displayed event dialog
if ($('#eventshow').is(':visible') && me.selected_event && me.selected_event.id == event.id)
event_show_dialog(event)
}
// refetch all calendars
else if (p.refetch) {
fc.fullCalendar('refetchEvents', undefined, true);
fetch_counts();
}
};
// modify query parameters for refresh requests
this.before_refresh = function(query)
{
var view = fc.fullCalendar('getView');
query.start = date2unixtime(view.visStart);
query.end = date2unixtime(view.visEnd);
if (this.search_query)
query.q = this.search_query;
return query;
};
// callback from server providing event counts
this.update_counts = function(p)
{
$.each(p.counts, function(cal, count) {
var li = calendars_list.get_item(cal),
bubble = $(li).children('.calendar').find('span.count');
if (!bubble.length && count > 0) {
bubble = $('<span>')
.addClass('count')
.appendTo($(li).children('.calendar').first())
}
if (count > 0) {
bubble.text(count).show();
}
else {
bubble.text('').hide();
}
});
};
// callback after an iTip message event was imported
this.itip_message_processed = function(data)
{
// remove temporary iTip source
fc.fullCalendar('removeEventSource', this.calendars['--invitation--itip']);
$('#eventshow:ui-dialog').dialog('close');
this.selected_event = null;
// refresh destination calendar source
this.refresh({ source:data.calendar, refetch:true });
this.unlock_saving();
// process 'after_action' in mail task
if (window.opener && window.opener.rcube_libcalendaring)
window.opener.rcube_libcalendaring.itip_message_processed(data);
};
// reload the calendar view by keeping the current date/view selection
this.reload_view = function()
{
var query = { view: fc.fullCalendar('getView').name },
date = fc.fullCalendar('getDate');
if (date)
query.date = date2unixtime(date);
rcmail.redirect(rcmail.url('', query));
}
// update browser location to remember current view
this.update_state = function()
{
var query = { view: current_view },
date = fc.fullCalendar('getDate');
if (date)
query.date = date2unixtime(date);
if (window.history.replaceState)
window.history.replaceState({}, document.title, rcmail.url('', query).replace('&_action=', ''));
};
this.resource_search = resource_search;
this.reset_resource_search = reset_resource_search;
this.add_resource2event = add_resource2event;
this.resource_data_load = resource_data_load;
this.resource_owner_load = resource_owner_load;
/*** event searching ***/
// execute search
this.quicksearch = function()
{
if (rcmail.gui_objects.qsearchbox) {
var q = rcmail.gui_objects.qsearchbox.value;
if (q != '') {
var id = 'search-'+q;
var sources = [];
if (me.quickview_active)
reset_quickview();
if (this._search_message)
rcmail.hide_message(this._search_message);
for (var sid in this.calendars) {
if (this.calendars[sid]) {
this.calendars[sid].url = this.calendars[sid].url.replace(/&q=.+/, '') + '&q=' + urlencode(q);
sources.push(sid);
}
}
id += '@'+sources.join(',');
// ignore if query didn't change
if (this.search_request == id) {
return;
}
// remember current view
else if (!this.search_request) {
this.default_view = fc.fullCalendar('getView').name;
}
this.search_request = id;
this.search_query = q;
// change to list view
fc.fullCalendar('option', 'listSections', 'month')
.fullCalendar('option', 'listRange', Math.max(60, settings['agenda_range']))
.fullCalendar('changeView', 'table');
update_agenda_toolbar();
// refetch events with new url (if not already triggered by changeView)
if (!this.is_loading)
fc.fullCalendar('refetchEvents');
}
else // empty search input equals reset
this.reset_quicksearch();
}
};
// reset search and get back to normal event listing
this.reset_quicksearch = function()
{
$(rcmail.gui_objects.qsearchbox).val('');
if (this._search_message)
rcmail.hide_message(this._search_message);
if (this.search_request) {
// hide bottom links of agenda view
fc.find('.fc-list-content > .fc-listappend').hide();
// restore original event sources and view mode from fullcalendar
fc.fullCalendar('option', 'listSections', settings['agenda_sections'])
.fullCalendar('option', 'listRange', settings['agenda_range']);
update_agenda_toolbar();
for (var sid in this.calendars) {
if (this.calendars[sid])
this.calendars[sid].url = this.calendars[sid].url.replace(/&q=.+/, '');
}
if (this.default_view)
fc.fullCalendar('changeView', this.default_view);
if (!this.is_loading)
fc.fullCalendar('refetchEvents');
this.search_request = this.search_query = null;
}
};
// callback if all sources have been fetched from server
this.events_loaded = function(count)
{
var addlinks, append = '';
// enhance list view when searching
if (this.search_request) {
if (!count) {
this._search_message = rcmail.display_message(rcmail.gettext('searchnoresults', 'calendar'), 'notice');
append = '<div class="message">' + rcmail.gettext('searchnoresults', 'calendar') + '</div>';
}
append += '<div class="fc-bottomlinks formlinks"></div>';
addlinks = true;
}
if (fc.fullCalendar('getView').name == 'table') {
var container = fc.find('.fc-list-content > .fc-listappend');
if (append) {
if (!container.length)
container = $('<div class="fc-listappend"></div>').appendTo(fc.find('.fc-list-content'));
container.html(append).show();
}
else if (container.length)
container.hide();
// add links to adjust search date range
if (addlinks) {
var lc = container.find('.fc-bottomlinks');
$('<a>').attr('href', '#').html(rcmail.gettext('searchearlierdates', 'calendar')).appendTo(lc).click(function(){
fc.fullCalendar('incrementDate', 0, -1, 0);
});
lc.append(" ");
$('<a>').attr('href', '#').html(rcmail.gettext('searchlaterdates', 'calendar')).appendTo(lc).click(function(){
var range = fc.fullCalendar('option', 'listRange');
if (range < 90) {
fc.fullCalendar('option', 'listRange', fc.fullCalendar('option', 'listRange') + 30).fullCalendar('render');
update_agenda_toolbar();
}
else
fc.fullCalendar('incrementDate', 0, 1, 0);
});
}
}
if (this.fisheye_date)
this.fisheye_view(this.fisheye_date);
};
// resize and reposition (center) the dialog window
this.dialog_resize = function(id, height, width)
{
var win = $(window), w = win.width(), h = win.height();
- $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) })
- .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
+ $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) });
};
// adjust calendar view size
this.view_resize = function()
{
var footer = fc.fullCalendar('getView').name == 'table' ? $('#agendaoptions').outerHeight() : 0;
fc.fullCalendar('option', 'height', $('#calendar').height() - footer);
};
// mark the given calendar folder as selected
this.select_calendar = function(id, nolistupdate)
{
if (!nolistupdate)
calendars_list.select(id);
// trigger event hook
rcmail.triggerEvent('selectfolder', { folder:id, prefix:'rcmlical' });
this.selected_calendar = id;
};
// register the given calendar to the current view
var add_calendar_source = function(cal)
{
var color, brightness, select, id = cal.id;
me.calendars[id] = $.extend({
url: rcmail.url('calendar/load_events', { source: id }),
className: 'fc-event-cal-'+id,
id: id
}, cal);
// choose black text color when background is bright, white otherwise
if (color = settings.event_coloring % 2 ? '' : '#' + cal.color) {
if (/^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.test(color)) {
// use information about brightness calculation found at
// http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/
brightness = (parseInt(RegExp.$1, 16) * 299 + parseInt(RegExp.$2, 16) * 587 + parseInt(RegExp.$3, 16) * 114) / 1000;
if (brightness > 125)
me.calendars[id].textColor = 'black';
}
me.calendars[id].color = color;
}
if (fc && (cal.active || cal.subscribed)) {
if (cal.active)
fc.fullCalendar('addEventSource', me.calendars[id]);
var submit = { id: id, active: cal.active ? 1 : 0 };
if (cal.subscribed !== undefined)
submit.permanent = cal.subscribed ? 1 : 0;
rcmail.http_post('calendar', { action:'subscribe', c:submit });
}
// insert to #calendar-select options if writeable
select = $('#edit-calendar');
if (fc && has_permission(cal, 'i') && select.length && !select.find('option[value="'+id+'"]').length) {
$('<option>').attr('value', id).html(cal.name).appendTo(select);
}
}
// fetch counts for some calendars from the server
var fetch_counts = function()
{
if (count_sources.length) {
setTimeout(function() {
rcmail.http_request('calendar/count', { source:count_sources });
}, 500);
}
};
/*** startup code ***/
// create list of event sources AKA calendars
var id, cal, active, event_sources = [];
for (id in rcmail.env.calendars) {
cal = rcmail.env.calendars[id];
active = cal.active || false;
add_calendar_source(cal);
// check active calendars
$('#rcmlical'+id+' > .calendar input').prop('checked', active);
if (active) {
event_sources.push(this.calendars[id]);
}
if (cal.counts) {
count_sources.push(id);
}
if (cal.editable && !this.selected_calendar) {
this.selected_calendar = id;
rcmail.enable_command('addevent', true);
}
}
// initialize treelist widget that controls the calendars list
var widget_class = window.kolab_folderlist || rcube_treelist_widget;
calendars_list = new widget_class(rcmail.gui_objects.calendarslist, {
id_prefix: 'rcmlical',
selectable: true,
save_state: true,
keyboard: false,
searchbox: '#calendarlistsearch',
search_action: 'calendar/calendar',
search_sources: [ 'folders', 'users' ],
search_title: rcmail.gettext('calsearchresults','calendar')
});
calendars_list.addEventListener('select', function(node) {
if (node && node.id && me.calendars[node.id]) {
me.select_calendar(node.id, true);
rcmail.enable_command('calendar-edit', 'calendar-showurl', true);
rcmail.enable_command('calendar-delete', me.calendars[node.id].editable);
rcmail.enable_command('calendar-remove', me.calendars[node.id] && me.calendars[node.id].removable);
}
});
calendars_list.addEventListener('insert-item', function(p) {
var cal = p.data;
if (cal && cal.id) {
add_calendar_source(cal);
// add css classes related to this calendar to document
if (cal.css) {
$('<style type="text/css"></style>')
.html(cal.css)
.appendTo('head');
}
}
});
calendars_list.addEventListener('subscribe', function(p) {
var cal;
if ((cal = me.calendars[p.id])) {
cal.subscribed = p.subscribed || false;
rcmail.http_post('calendar', { action:'subscribe', c:{ id:p.id, active:cal.active?1:0, permanent:cal.subscribed?1:0 } });
}
});
calendars_list.addEventListener('remove', function(p) {
if (me.calendars[p.id] && me.calendars[p.id].removable) {
me.calendar_remove(me.calendars[p.id]);
}
});
calendars_list.addEventListener('search-complete', function(data) {
if (data.length)
rcmail.display_message(rcmail.gettext('nrcalendarsfound','calendar').replace('$nr', data.length), 'voice');
else
rcmail.display_message(rcmail.gettext('nocalendarsfound','calendar'), 'info');
});
calendars_list.addEventListener('click-item', function(event) {
// handle clicks on quickview icon: temprarily add this source and open in quickview
if ($(event.target).hasClass('quickview') && event.data) {
if (!me.calendars[event.data.id]) {
event.data.readonly = true;
event.data.active = false;
event.data.subscribed = false;
add_calendar_source(event.data);
}
me.quickview(event.data.id, event.shiftKey || event.metaKey || event.ctrlKey);
return false;
}
});
// init (delegate) event handler on calendar list checkboxes
$(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e) {
e.stopPropagation();
if (me.quickview_active) {
this.checked = !this.checked;
return false;
}
var id = this.value;
if (me.calendars[id]) { // add or remove event source on click
var action;
if (this.checked) {
action = 'addEventSource';
me.calendars[id].active = true;
}
else {
action = 'removeEventSource';
me.calendars[id].active = false;
}
// adjust checked state of original list item
if (calendars_list.is_search()) {
calendars_list.container.find('input[value="'+id+'"]').prop('checked', this.checked);
}
// add/remove event source
fc.fullCalendar(action, me.calendars[id]);
rcmail.http_post('calendar', { action:'subscribe', c:{ id:id, active:me.calendars[id].active?1:0 } });
}
})
.on('keypress', 'input[type=checkbox]', function(e) {
// select calendar on <Enter>
if (e.keyCode == 13) {
calendars_list.select(this.value);
return rcube_event.cancel(e);
}
})
// init (delegate) event handler on quickview links
.on('click', 'a.quickview', function(e) {
var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
if (calendars_list.is_search())
id = id.replace(/--xsR$/, '');
if (me.calendars[id])
me.quickview(id, e.shiftKey || e.metaKey || e.ctrlKey);
if (!rcube_event.is_keyboard(e) && this.blur)
this.blur();
e.stopPropagation();
return false;
});
// register dbl-click handler to open calendar edit dialog
$(rcmail.gui_objects.calendarslist).on('dblclick', ':not(.virtual) > .calname', function(e){
var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
me.calendar_edit_dialog(me.calendars[id]);
});
// select default calendar
if (settings.default_calendar && this.calendars[settings.default_calendar] && this.calendars[settings.default_calendar].editable)
this.selected_calendar = settings.default_calendar;
if (this.selected_calendar)
this.select_calendar(this.selected_calendar);
var viewdate = new Date();
if (rcmail.env.date)
viewdate.setTime(fromunixtime(rcmail.env.date));
// add source with iTip event data for rendering
if (rcmail.env.itip_events && rcmail.env.itip_events.length) {
me.calendars['--invitation--itip'] = {
events: rcmail.env.itip_events,
className: 'fc-event-cal---invitation--itip',
color: '#fff',
textColor: '#333',
editable: false,
rights: 'lrs',
attendees: true
};
event_sources.push(me.calendars['--invitation--itip']);
}
// initalize the fullCalendar plugin
var fc = $('#calendar').fullCalendar($.extend({}, fullcalendar_defaults, {
header: {
right: 'prev,next today',
center: 'title',
left: 'agendaDay,agendaWeek,month,table'
},
date: viewdate.getDate(),
month: viewdate.getMonth(),
year: viewdate.getFullYear(),
height: $('#calendar').height(),
eventSources: event_sources,
selectable: true,
selectHelper: false,
loading: function(isLoading) {
me.is_loading = isLoading;
this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading);
// trigger callback
if (!isLoading)
me.events_loaded($(this).fullCalendar('clientEvents').length);
},
// callback for date range selection
select: function(start, end, allDay, e, view) {
var range_select = (!allDay || start.getDate() != end.getDate())
if (dialog_check(e) && range_select)
event_edit_dialog('new', { start:start, end:end, allDay:allDay, calendar:me.selected_calendar });
if (range_select || ignore_click)
view.calendar.unselect();
},
// callback for clicks in all-day box
dayClick: function(date, allDay, e, view) {
var now = new Date().getTime();
if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) { // emulate double-click on day
var enddate = new Date(); enddate.setTime(date.getTime() + DAY_MS - 60000);
return event_edit_dialog('new', { start:date, end:enddate, allDay:allDay, calendar:me.selected_calendar });
}
if (!ignore_click) {
view.calendar.gotoDate(date);
if (day_clicked && new Date(day_clicked).getMonth() != date.getMonth())
view.calendar.select(date, date, allDay);
}
day_clicked = date.getTime();
day_clicked_ts = now;
},
// callback when an event was dragged and finally dropped
eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) {
if (event.end == null || event.end.getTime() < event.start.getTime()) {
event.end = new Date(event.start.getTime() + (allDay ? DAY_MS : HOUR_MS));
}
// moved to all-day section: set times to 12:00 - 13:00
if (allDay && !event.allDay) {
event.start.setHours(12);
event.start.setMinutes(0);
event.start.setSeconds(0);
event.end.setHours(13);
event.end.setMinutes(0);
event.end.setSeconds(0);
}
// moved from all-day section: set times to working hours
else if (event.allDay && !allDay) {
var newstart = event.start.getTime();
revertFunc(); // revert to get original duration
var numdays = Math.max(1, Math.round((event.end.getTime() - event.start.getTime()) / DAY_MS)) - 1;
event.start = new Date(newstart);
event.end = new Date(newstart + numdays * DAY_MS);
event.end.setHours(settings['work_end'] || 18);
event.end.setMinutes(0);
if (event.end.getTime() < event.start.getTime())
event.end = new Date(newstart + HOUR_MS);
}
// send move request to server
var data = {
id: event.id,
calendar: event.calendar,
start: date2servertime(event.start),
end: date2servertime(event.end),
allday: allDay?1:0
};
update_event_confirm('move', event, data);
},
// callback for event resizing
eventResize: function(event, delta) {
// sanitize event dates
if (event.allDay)
event.start.setHours(12);
if (!event.end || event.end.getTime() < event.start.getTime())
event.end = new Date(event.start.getTime() + HOUR_MS);
// send resize request to server
var data = {
id: event.id,
calendar: event.calendar,
start: date2servertime(event.start),
end: date2servertime(event.end)
};
update_event_confirm('resize', event, data);
},
viewDisplay: function(view) {
$('#agendaoptions')[view.name == 'table' ? 'show' : 'hide']();
if (minical) {
window.setTimeout(function(){ minical.datepicker('setDate', fc.fullCalendar('getDate')); }, exec_deferred);
if (view.name != current_view)
me.view_resize();
current_view = view.name;
me.update_state();
}
},
viewRender: function(view) {
if (fc && view.name == 'month')
fc.fullCalendar('option', 'maxHeight', Math.floor((view.element.parent().height()-18) / 6) - 35);
}
}));
// if start date is changed, shift end date according to initial duration
var shift_enddate = function(dateText) {
var newstart = parse_datetime('0', dateText);
var newend = new Date(newstart.getTime() + $('#edit-startdate').data('duration') * 1000);
$('#edit-enddate').val($.fullCalendar.formatDate(newend, me.settings['date_format']));
event_times_changed();
};
// Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
// Uses the default $.datepicker.iso8601Week() function but takes firstDay setting into account.
// This is a temporary fix until http://bugs.jqueryui.com/ticket/8420 is resolved.
var iso8601Week = datepicker_settings.calculateWeek = function(date) {
var mondayOffset = Math.abs(1 - datepicker_settings.firstDay);
return $.datepicker.iso8601Week(new Date(date.getTime() + mondayOffset * 86400000));
};
var minical;
var init_calendar_ui = function()
{
// initialize small calendar widget using jQuery UI datepicker
minical = $('#datepicker').datepicker($.extend(datepicker_settings, {
inline: true,
showWeek: true,
changeMonth: true,
changeYear: true,
onSelect: function(dateText, inst) {
ignore_click = true;
var d = minical.datepicker('getDate'); //parse_datetime('0:0', dateText);
fc.fullCalendar('gotoDate', d).fullCalendar('select', d, d, true);
},
onChangeMonthYear: function(year, month, inst) {
minical.data('year', year).data('month', month);
},
beforeShowDay: function(date) {
var view = fc.fullCalendar('getView');
var active = view.visStart && date.getTime() >= view.visStart.getTime() && date.getTime() < view.visEnd.getTime();
return [ true, (active ? 'ui-datepicker-activerange ui-datepicker-active-' + view.name : ''), ''];
}
})) // set event handler for clicks on calendar week cell of the datepicker widget
.on('click', 'td.ui-datepicker-week-col', function(e) {
var cell = $(e.target);
if (e.target.tagName == 'TD') {
var base_date = minical.datepicker('getDate');
if (minical.data('month'))
base_date.setMonth(minical.data('month')-1);
if (minical.data('year'))
base_date.setYear(minical.data('year'));
base_date.setHours(12);
base_date.setDate(base_date.getDate() - ((base_date.getDay() + 6) % 7) + datepicker_settings.firstDay);
var base_kw = iso8601Week(base_date),
target_kw = parseInt(cell.html()),
wdiff = target_kw - base_kw;
if (wdiff > 10) // year jump
base_date.setYear(base_date.getFullYear() - 1);
else if (wdiff < -10)
base_date.setYear(base_date.getFullYear() + 1);
// select monday of the chosen calendar week
var day_off = base_date.getDay() - datepicker_settings.firstDay,
date = new Date(base_date.getTime() - day_off * DAY_MS + wdiff * 7 * DAY_MS);
fc.fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
minical.datepicker('setDate', date);
}
});
minical.find('.ui-datepicker-inline').attr('aria-labelledby', 'aria-label-minical');
if (rcmail.env.date) {
var viewdate = new Date();
viewdate.setTime(fromunixtime(rcmail.env.date));
minical.datepicker('setDate', viewdate);
}
// init event dialog
$('#eventtabs').tabs({
activate: function(event, ui) {
if (ui.newPanel.selector == '#event-panel-attendees' || ui.newPanel.selector == '#event-panel-resources') {
var tab = ui.newPanel.selector == '#event-panel-resources' ? 'resource' : 'attendee';
if (!rcube_event.is_keyboard(event))
$('#edit-'+tab+'-name').select();
// update free-busy status if needed
if (freebusy_ui.needsupdate && me.selected_event)
update_freebusy_status(me.selected_event);
// add current user as organizer if non added yet
if (!event_attendees.length) {
add_attendee($.extend({ role:'ORGANIZER' }, settings.identity));
$('#edit-attendees-form .attendees-invitebox').show();
}
}
// reset autocompletion on tab change (#3389)
if (ui.oldPanel.selector == '#event-panel-attendees' || ui.oldPanel.selector == '#event-panel-resources') {
rcmail.ksearch_blur();
}
}
});
$('#edit-enddate').datepicker(datepicker_settings);
$('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); });
$('#edit-enddate').datepicker('option', 'onSelect', event_times_changed).change(event_times_changed);
$('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); event_times_changed(); });
// configure drop-down menu on time input fields based on jquery UI autocomplete
$('#edit-starttime, #edit-endtime, #eventedit input.edit-alarm-time').each(function() {
me.init_time_autocomplete(this, {
container: '#eventedit',
change: event_times_changed
});
});
// adjust end time when changing start
$('#edit-starttime').change(function(e) {
var dstart = $('#edit-startdate'),
newstart = parse_datetime(this.value, dstart.val()),
newend = new Date(newstart.getTime() + dstart.data('duration') * 1000);
$('#edit-endtime').val($.fullCalendar.formatDate(newend, me.settings['time_format']));
$('#edit-enddate').val($.fullCalendar.formatDate(newend, me.settings['date_format']));
event_times_changed();
});
// register events on alarms and recurrence fields
me.init_alarms_edit('#edit-alarms');
me.init_recurrence_edit('#eventedit');
// reload free-busy status when changing the organizer identity
$('#eventedit').on('change', '#edit-identities-list', function(e) {
var email = settings.identities[$(this).val()],
icon = $(this).closest('tr').find('img.availabilityicon');
if (email && icon.length) {
icon.attr('data-email', email);
check_freebusy_status(icon, email, me.selected_event);
}
});
$('#event-export-startdate').datepicker(datepicker_settings);
// init attendees autocompletion
var ac_props;
// parallel autocompletion
if (rcmail.env.autocomplete_threads > 0) {
ac_props = {
threads: rcmail.env.autocomplete_threads,
sources: rcmail.env.autocomplete_sources
};
}
rcmail.init_address_input_events($('#edit-attendee-name'), ac_props);
rcmail.addEventListener('autocomplete_insert', function(e) {
var success = false;
if (e.field.name == 'participant') {
success = add_attendees(e.insert, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:(e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL') });
}
else if (e.field.name == 'resource' && e.data && e.data.email) {
success = add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }));
}
if (e.field && success) {
e.field.value = '';
}
});
$('#edit-attendee-add').click(function(){
var input = $('#edit-attendee-name');
rcmail.ksearch_blur();
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) {
input.val('');
}
});
rcmail.init_address_input_events($('#edit-resource-name'), { action:'calendar/resources-autocomplete' });
$('#edit-resource-add').click(function(){
var input = $('#edit-resource-name');
rcmail.ksearch_blur();
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })) {
input.val('');
}
});
$('#edit-resource-find').click(function(){
event_resources_dialog();
return false;
});
// handle change of "send invitations" checkbox
$('#edit-attendees-invite').change(function() {
$('#edit-attendees-donotify,input.edit-attendee-reply').prop('checked', this.checked);
// hide/show comment field
$('#eventedit .attendees-commentbox')[this.checked ? 'show' : 'hide']();
});
// delegate change event to "send invitations" checkbox
$('#edit-attendees-donotify').change(function() {
$('#edit-attendees-invite').click();
return false;
});
$('#edit-attendee-schedule').click(function(){
event_freebusy_dialog();
});
$('#shedule-freebusy-prev').html(bw.ie6 ? '&lt;&lt;' : '&#9668;').button().click(function(){ render_freebusy_grid(-1); });
$('#shedule-freebusy-next').html(bw.ie6 ? '&gt;&gt;' : '&#9658;').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset();
$('#shedule-find-prev').button().click(function(){ freebusy_find_slot(-1); });
$('#shedule-find-next').button().click(function(){ freebusy_find_slot(1); });
$('#schedule-freebusy-workinghours').click(function(){
freebusy_ui.workinhoursonly = this.checked;
$('#workinghourscss').remove();
if (this.checked)
$('<style type="text/css" id="workinghourscss"> td.offhours { opacity:0.3; filter:alpha(opacity=30) } </style>').appendTo('head');
});
$('#event-rsvp input.button').click(function(e) {
event_rsvp(this)
});
$('#eventedit input.edit-recurring-savemode').change(function(e) {
var sel = $('input.edit-recurring-savemode:checked').val(),
disabled = sel == 'current' || sel == 'future';
$('#event-panel-recurrence input, #event-panel-recurrence select, #event-panel-attachments input').prop('disabled', disabled);
$('#event-panel-recurrence, #event-panel-attachments')[(disabled?'addClass':'removeClass')]('disabled');
})
$('#eventshow .changersvp').click(function(e) {
var d = $('#eventshow'),
h = -$(this).closest('.event-line').toggle().height();
$('#event-rsvp').slideDown(300, function() {
h += $(this).height();
me.dialog_resize(d.get(0), d.height() + h, d.outerWidth() - 50);
});
return false;
})
// register click handler for message links
$('#edit-event-links, #event-links').on('click', 'li a.messagelink', function(e) {
rcmail.open_window(this.href);
if (!rcube_event.is_keyboard(e) && this.blur)
this.blur();
return false;
});
// register click handler for message delete buttons
$('#edit-event-links').on('click', 'li a.delete', function(e) {
remove_link(e.target);
return false;
});
$('#agenda-listrange').change(function(e){
settings['agenda_range'] = parseInt($(this).val());
fc.fullCalendar('option', 'listRange', settings['agenda_range']).fullCalendar('render');
// TODO: save new settings in prefs
}).val(settings['agenda_range']);
$('#agenda-listsections').change(function(e){
settings['agenda_sections'] = $(this).val();
fc.fullCalendar('option', 'listSections', settings['agenda_sections']).fullCalendar('render');
// TODO: save new settings in prefs
}).val(fc.fullCalendar('option', 'listSections'));
// hide event dialog when clicking somewhere into document
$(document).bind('mousedown', dialog_check);
rcmail.set_busy(false, 'loading', ui_loading);
}
// initialize more UI elements (deferred)
window.setTimeout(init_calendar_ui, exec_deferred);
// fetch counts for some calendars
fetch_counts();
// add proprietary css styles if not IE
if (!bw.ie)
$('div.fc-content').addClass('rcube-fc-content');
// IE supresses 2nd click event when double-clicking
if (bw.ie && bw.vendver < 9) {
$('div.fc-content').bind('dblclick', function(e){
if (!$(this).hasClass('fc-widget-header') && fc.fullCalendar('getView').name != 'table') {
var date = fc.fullCalendar('getDate');
var enddate = new Date(); enddate.setTime(date.getTime() + DAY_MS - 60000);
event_edit_dialog('new', { start:date, end:enddate, allDay:true, calendar:me.selected_calendar });
}
});
}
} // end rcube_calendar class
/* calendar plugin initialization */
window.rcmail && rcmail.addEventListener('init', function(evt) {
// configure toolbar buttons
rcmail.register_command('addevent', function(){ cal.add_event(); }, true);
rcmail.register_command('print', function(){ cal.print_calendars(); }, true);
// configure list operations
rcmail.register_command('calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('calendar-delete', function(){ cal.calendar_delete(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('events-import', function(){ cal.import_events(cal.calendars[cal.selected_calendar]); }, true);
rcmail.register_command('calendar-showurl', function(){ cal.showurl(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('event-download', function(){ cal.event_download(cal.selected_event); }, true);
rcmail.register_command('event-sendbymail', function(p, obj, e){ cal.event_sendbymail(cal.selected_event, e); }, true);
rcmail.register_command('event-copy', function(){ cal.event_copy(cal.selected_event); }, true);
rcmail.register_command('event-history', function(p, obj, e){ cal.event_history_dialog(cal.selected_event); }, false);
// search and export events
rcmail.register_command('export', function(){ cal.export_events(cal.calendars[cal.selected_calendar]); }, true);
rcmail.register_command('search', function(){ cal.quicksearch(); }, true);
rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
// resource invitation dialog
rcmail.register_command('search-resource', function(){ cal.resource_search(); }, true);
rcmail.register_command('reset-resource-search', function(){ cal.reset_resource_search(); }, true);
rcmail.register_command('add-resource', function(){ cal.add_resource2event(); }, false);
// register callback commands
rcmail.addEventListener('plugin.refresh_source', function(data) { cal.calendar_refresh_source(data); });
rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); });
rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); });
rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); });
rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); });
rcmail.addEventListener('plugin.update_counts', function(p){ cal.update_counts(p); });
rcmail.addEventListener('plugin.reload_view', function(p){ cal.reload_view(p); });
rcmail.addEventListener('plugin.resource_data', function(p){ cal.resource_data_load(p); });
rcmail.addEventListener('plugin.resource_owner', function(p){ cal.resource_owner_load(p); });
rcmail.addEventListener('plugin.render_event_changelog', function(data){ cal.render_event_changelog(data); });
rcmail.addEventListener('plugin.event_show_diff', function(data){ cal.event_show_diff(data); });
rcmail.addEventListener('plugin.close_history_dialog', function(data){ cal.close_history_dialog(); });
rcmail.addEventListener('plugin.event_show_revision', function(data){ cal.event_show_dialog(data, null, true); });
rcmail.addEventListener('plugin.itip_message_processed', function(data){ cal.itip_message_processed(data); });
rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); });
// let's go
var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
$(window).resize(function(e) {
// check target due to bugs in jquery
// http://bugs.jqueryui.com/ticket/7514
// http://bugs.jquery.com/ticket/9841
if (e.target == window) {
cal.view_resize();
}
}).resize();
// show calendars list when ready
$('#calendars').css('visibility', 'inherit');
// show toolbar
$('#toolbar').show();
});
diff --git a/plugins/kolab_files/kolab_files.js b/plugins/kolab_files/kolab_files.js
index 620a7ed2..1bc00217 100644
--- a/plugins/kolab_files/kolab_files.js
+++ b/plugins/kolab_files/kolab_files.js
@@ -1,3759 +1,3758 @@
/**
* Kolab files plugin
*
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2011-2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program 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.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
window.rcmail && window.files_api && rcmail.addEventListener('init', function() {
if (rcmail.task == 'mail') {
// mail compose
if (rcmail.env.action == 'compose') {
kolab_files_from_cloud_widget($('#compose-attachments > div'));
// register some commands to skip warning message on compose page
$.merge(rcmail.env.compose_commands, ['files-list', 'files-sort', 'files-search', 'files-search-reset']);
}
// mail preview
else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
var attachment_list = $('#attachment-list');
if ($('li', attachment_list).length) {
var link = $('<a href="#" class="button filesaveall">')
.text(rcmail.gettext('kolab_files.saveall'))
.click(function() { kolab_directory_selector_dialog(); })
.insertAfter(attachment_list);
}
rcmail.addEventListener('menu-open', kolab_files_attach_menu_open);
rcmail.enable_command('folder-create', true);
}
// attachment preview
else if (rcmail.env.action == 'get') {
rcmail.enable_command('folder-create', true);
}
if (!rcmail.env.action || rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
// add "attach from cloud" button for event/task dialog in mail
rcmail.addEventListener('plugin.mail2event_dialog', function() {
if (!$('#calendar-attachment-form input.fromcloud').length)
kolab_files_from_cloud_widget($('#calendar-attachment-form > div.buttons'));
});
}
}
else if (rcmail.task == 'calendar') {
// add "attach from cloud" button for event dialog
if (!rcmail.env.action)
kolab_files_from_cloud_widget($('#calendar-attachment-form > div.buttons'));
}
else if (rcmail.task == 'tasks') {
// add "attach from cloud" button for task dialog
if (!rcmail.env.action)
kolab_files_from_cloud_widget($('#taskedit-attachment-form > div.buttons'));
}
else if (rcmail.task == 'files') {
if (rcmail.gui_objects.fileslist) {
rcmail.fileslist = new rcube_list_widget(rcmail.gui_objects.fileslist, {
multiselect: true,
draggable: true,
keyboard: true,
column_movable: rcmail.env.files_col_movable,
dblclick_time: rcmail.dblclick_time
});
rcmail.fileslist.addEventListener('dblclick', function(o) { kolab_files_list_dblclick(o); })
.addEventListener('select', function(o) { kolab_files_list_select(o); })
.addEventListener('keypress', function(o) { kolab_files_list_keypress(o); })
.addEventListener('dragstart', function(e) { kolab_files_drag_start(e); })
.addEventListener('dragmove', function(e) { kolab_files_drag_move(e); })
.addEventListener('dragend', function(e) { kolab_files_drag_end(e); })
.addEventListener('column_replace', function(e) { kolab_files_set_coltypes(e, 'files'); })
.addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); });
rcmail.enable_command('menu-open', 'menu-save', 'files-sort', 'files-search', 'files-search-reset', 'folder-create', true);
rcmail.fileslist.init();
kolab_files_list_coltypes('files');
kolab_files_drag_drop_init($(rcmail.gui_objects.fileslist).parents('.droptarget'));
}
if (rcmail.gui_objects.sessionslist) {
rcmail.sessionslist = new rcube_list_widget(rcmail.gui_objects.sessionslist, {
keyboard: true,
column_movable: rcmail.env.sessions_col_movable,
dblclick_time: rcmail.dblclick_time
});
rcmail.sessionslist.addEventListener('dblclick', function(o) { kolab_files_sessions_list_dblclick(o); })
.addEventListener('select', function(o) { kolab_files_sessions_list_select(o); })
.addEventListener('keypress', function(o) { kolab_files_sessions_list_keypress(o); })
.addEventListener('column_replace', function(e) { kolab_files_set_coltypes(e, 'sessions'); })
.addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); });
rcmail.sessionslist.init();
kolab_files_list_coltypes('sessions');
}
// "one file only" commands
rcmail.env.file_commands = ['files-get', 'files-rename'];
// "one or more file" commands
rcmail.env.file_commands_all = ['files-delete', 'files-move', 'files-copy'];
if (rcmail.env.action == 'open' || rcmail.env.action == 'edit') {
rcmail.enable_command('files-get', true);
rcmail.enable_command('files-delete', rcmail.env.file_data.writable);
}
else {
rcmail.enable_command('folder-mount', rcmail.env.external_sources);
}
}
kolab_files_init();
});
/**********************************************************/
/********* Shared functionality **********/
/**********************************************************/
// Initializes API object
function kolab_files_init()
{
if (window.file_api)
return;
var manticore_config = {};
// Initialize application object (don't change var name!)
file_api = $.extend(new files_api(), new kolab_files_ui());
file_api.set_env({
token: kolab_files_token(),
url: rcmail.env.files_url,
sort_col: 'name',
sort_reverse: false,
search_threads: rcmail.env.search_threads,
resources_dir: rcmail.assets_path('program/resources'),
caps: rcmail.env.files_caps,
supported_mimetypes: rcmail.env.file_mimetypes
});
file_api.translations = rcmail.labels;
if (rcmail.task == 'files') {
if (rcmail.env.action == 'edit' && rcmail.env.file_data.viewer && rcmail.env.file_data.viewer.manticore) {
manticore_config = {
// UI elements
iframe: $('#fileframe').get(0),
export_menu: rcmail.gui_objects.exportmenu ? $('ul', rcmail.gui_objects.exportmenu).get(0) : null,
title_input: $('#document-title').get(0),
members_list: $('#members').get(0),
photo_url: '?_task=addressbook&_action=photo&_error=1&_email=%email',
photo_default_url: rcmail.env.photo_placeholder,
// events
ready: function(data) { manticore_init(); },
sessionClosed: function(data) { return manticore_close(); }
};
if (rcmail.env.file_data.writable)
manticore_config.documentChanged = function(data) { rcmail.enable_command('document-save', true); };
}
else if (rcmail.env.action == 'open') {
// initialize folders list (for dialogs)
file_api.folder_list();
// get ongoing sessions
file_api.request('folder_info', {folder: file_api.file_path(rcmail.env.file), sessions: 1}, 'folder_info_response');
}
else {
file_api.folder_list();
file_api.browser_capabilities_check();
}
}
if (rcmail.env.files_caps && rcmail.env.files_caps.MANTICORE && !rcmail.env.framed)
$.extend(manticore_config, {
// invitation notifications
api: file_api,
owner: rcmail.env.files_user,
interval: rcmail.env.files_interval || 60,
invitationMore: true,
invitationChange: manticore_invitation_handler
});
$.extend(manticore_config, {
// notifications/alerts
gettext: function(label) { return rcmail.get_label('kolab_files.' + label); },
set_busy: function(state, message) { return rcmail.set_busy(state, message ? 'kolab_files.' + message : ''); },
hide_message: function(id) { return rcmail.hide_message(id); },
display_message: function(label, type, is_txt, timeout) {
if (!is_txt)
label = 'kolab_files.' + label;
return rcmail.display_message(label, type, timeout * 1000);
}
});
manticore = new manticore_api(manticore_config);
};
// returns API authorization token
function kolab_files_token()
{
// consider the token from parent window more reliable (fresher) than in framed window
// it's because keep-alive is not requested in frames
return window.parent && parent.rcmail && parent.rcmail.env.files_token ? parent.rcmail.env.files_token : rcmail.env.files_token;
};
function kolab_files_from_cloud_widget(elem)
{
var input = $('<input class="button fromcloud" type="button">')
.attr('tabindex', $('input', elem).attr('tabindex') || 0)
.val(rcmail.gettext('kolab_files.fromcloud'))
.click(function() { kolab_files_selector_dialog(); })
.appendTo(elem);
if (rcmail.gui_objects.fileslist) {
rcmail.fileslist = new rcube_list_widget(rcmail.gui_objects.fileslist, {
multiselect: true,
keyboard: true,
column_movable: false,
dblclick_time: rcmail.dblclick_time
});
rcmail.fileslist.addEventListener('select', function(o) { kolab_files_list_select(o); })
.addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); });
rcmail.enable_command('files-sort', 'files-search', 'files-search-reset', true);
rcmail.fileslist.init();
kolab_files_list_coltypes();
}
}
// folder selection dialog
function kolab_directory_selector_dialog(id)
{
var dialog = $('#files-dialog'),
input = $('#file-save-as-input'),
form = $('#file-save-as'),
list = $('#folderlistbox'),
buttons = {}, label = 'saveto',
win = window, fn;
// attachment is specified
if (id) {
var attach = $('#attach' + id + '> a').first(),
filename = attach.attr('title');
if (!filename) {
attach = attach.clone();
$('.attachment-size', attach).remove();
filename = $.trim(attach.text());
}
form.show();
dialog.addClass('saveas');
input.val(filename);
}
// attachment preview page
else if (rcmail.env.action == 'get') {
id = rcmail.env.part;
form.show();
dialog.addClass('saveas');
input.val(rcmail.env.filename);
}
else {
form.hide();
dialog.removeClass('saveas');
label = 'saveall';
}
$('#foldercreatelink').attr('tabindex', 0);
buttons[rcmail.gettext('kolab_files.save')] = function () {
if (!file_api.env.folder)
return;
var lock = rcmail.set_busy(true, 'saving'),
request = {
act: 'save-file',
source: rcmail.env.mailbox,
uid: rcmail.env.uid,
dest: file_api.env.folder
};
if (id) {
request.id = id;
request.name = input.val();
}
rcmail.http_post('plugin.kolab_files', request, lock);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
if (!rcmail.env.folders_loaded) {
fn = function() {
rcmail.env.folder_list_selector = '#files-dialog #files-folder-list';
rcmail.env.folder_search_selector = '#files-dialog #foldersearch';
file_api.folder_list({writable: 1});
rcmail.env.folders_loaded = true;
};
}
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.' + label),
buttons: buttons,
button_classes: ['mainaction'],
minWidth: 250,
minHeight: 300,
height: 400,
width: 300
}, fn);
// "enable" folder creation when dialog is displayed in parent window
if (rcmail.is_framed()) {
parent.rcmail.enable_command('folder-create', true);
parent.rcmail.folder_create = function() {
win.kolab_files_folder_create_dialog();
};
}
};
// file selection dialog
function kolab_files_selector_dialog()
{
var dialog = $('#files-compose-dialog'), buttons = {};
buttons[rcmail.gettext('kolab_files.attachsel')] = function () {
var list = [];
$('#fileslist tr.selected').each(function() {
list.push($(this).data('file'));
});
kolab_dialog_close(this);
if (list.length) {
// display upload indicator and cancel button
var content = '<span>' + rcmail.get_label('kolab_files.attaching') + '</span>',
id = new Date().getTime();
rcmail.add2attachment_list(id, {name:'', html:content, classname:'uploading', complete:false});
// send request
rcmail.http_post('plugin.kolab_files', {
act: 'attach-file',
files: list,
id: rcmail.env.compose_id,
uploadid: id
});
}
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.selectfiles'),
buttons: buttons,
button_classes: ['mainaction'],
minWidth: 500,
minHeight: 300,
width: 700,
height: 500
}, function() { rcmail.fileslist.resize(); });
if (!rcmail.env.files_loaded) {
rcmail.env.folder_list_selector = '#files-compose-dialog #files-folder-list';
rcmail.env.folder_search_selector = '#files-compose-dialog #foldersearch';
file_api.folder_list();
rcmail.env.files_loaded = true;
}
else {
rcmail.fileslist.clear_selection();
}
};
function kolab_files_attach_menu_open(p)
{
if (!p || !p.props || p.props.menu != 'attachmentmenu')
return;
var id = p.props.id;
$('#attachmenusaveas').unbind('click').attr('onclick', '').click(function(e) {
return kolab_directory_selector_dialog(id);
});
};
// folder creation dialog
function kolab_files_folder_create_dialog()
{
var dialog = $('#files-folder-create-dialog'),
buttons = {},
select = $('select[name="parent"]', dialog).html(''),
input = $('input[name="name"]', dialog).val('');
buttons[rcmail.gettext('kolab_files.create')] = function () {
var folder = '', name = input.val(), parent = select.val();
if (!name)
return;
if (parent)
folder = parent + file_api.env.directory_separator;
folder += name;
file_api.folder_create(folder);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.foldercreate'),
buttons: buttons,
button_classes: ['mainaction']
});
// Fix submitting form with Enter
$('form', dialog).submit(kolab_dialog_submit_handler);
// build parent selector
file_api.folder_select_element(select, {empty: true, writable: true});
};
// folder edit dialog
function kolab_files_folder_edit_dialog()
{
var dialog = $('#files-folder-edit-dialog'),
buttons = {},
separator = file_api.env.directory_separator,
arr = file_api.env.folder.split(separator),
folder = arr.pop(),
path = arr.join(separator),
select = $('select[name="parent"]', dialog).html(''),
input = $('input[name="name"]', dialog).val(folder);
buttons[rcmail.gettext('kolab_files.save')] = function () {
var folder = '', name = input.val(), parent = select.val();
if (!name)
return;
if (parent)
folder = parent + separator;
folder += name;
file_api.folder_rename(file_api.env.folder, folder);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.folderedit'),
buttons: buttons,
button_classes: ['mainaction']
});
// Fix submitting form with Enter
$('form', dialog).submit(kolab_dialog_submit_handler);
// build parent selector
file_api.folder_select_element(select, {selected: path, empty: true});
};
// folder mounting dialog
function kolab_files_folder_mount_dialog()
{
var args = {buttons: {}, title: rcmail.gettext('kolab_files.foldermount')},
dialog = $('#files-folder-mount-dialog'),
input = $('#folder-mount-name').val('');
args.buttons[rcmail.gettext('kolab_files.save')] = function () {
var args = {}, folder = input.val(),
driver = $('input[name="driver"]:checked', dialog).val();
if (!folder || !driver)
return;
args.folder = folder;
args.driver = driver;
$('#source-' + driver + ' input').each(function() {
if (this.name.startsWith(driver + '[')) {
args[this.name.substring(driver.length + 1, this.name.length - 1)] = this.value;
}
});
$('.auth-options input', dialog).each(function() {
args[this.name] = this.type == 'checkbox' && !this.checked ? '' : this.value;
});
file_api.folder_mount(args);
kolab_dialog_close(this);
};
args.buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// close folderoption menu
rcmail.hide_menu('folderoptions');
// initialize drivers list
if (!rcmail.drivers_list_initialized) {
rcmail.drivers_list_initialized = true;
$('td.source', dialog).each(function() {
$(this).click(function() {
$('td.selected', dialog).removeClass('selected');
dialog.find('.driverform').hide();
$(this).addClass('selected').find('.driverform').show();
$('input[type="radio"]', this).prop('checked', true);
});
});
}
args.button_classes = ['mainaction'];
// show dialog window
kolab_dialog_show(dialog, args, function() {
$('td.source:first', dialog).click();
input.focus();
});
};
// file edit dialog
function kolab_files_file_edit_dialog(file, sessions, readonly)
{
var content = [], items = [], height = 300,
dialog = $('#files-file-edit-dialog'),
buttons = {}, name = file_api.file_name(file),
title = rcmail.gettext('kolab_files.editfiledialog'),
mainaction = rcmail.gettext('kolab_files.select'),
item_fn = function(id, txt, classes) {
return $('<label>').attr('class', 'session' + (classes ? ' ' + classes : ''))
.append($('<input>').attr({name: 'opt', value: id, type: 'radio'})).append($('<span>').text(txt));
},
select_fn = function(dlg) {
var session, input = $('input:checked', dialog), id = input.val();
if (dlg)
kolab_dialog_close(dlg);
if (id && input.parent().is('.session.request')) {
manticore.invitation_request({session_id: id});
return;
}
if (readonly && (id == 0 || !input.length))
return kolab_files_file_create_dialog(file);
rcmail.files_edit(id ? id : true);
};
// Create sessions selection
if (sessions && sessions.length) {
items.push($('<div>').text(rcmail.gettext('kolab_files.editfilesessions')));
// first display owned sessions, then invited, other at the end
$.each(sessions, function() {
if (this.is_owner) {
var txt = rcmail.gettext('kolab_files.ownedsession');
items.push(item_fn(this.id, txt, 'owner'));
}
});
if (items.length == 1)
items.push(item_fn(0, rcmail.gettext('kolab_files.newsession' + (readonly ? 'ro' : ''))));
$.each(sessions, function() {
if (this.is_invited) {
var txt = rcmail.gettext('kolab_files.invitedsession')
.replace('$user', this.owner_name ? this.owner_name : this.owner);
items.push(item_fn(this.id, txt, 'invited'));
}
});
$.each(sessions, function() {
if (!this.is_owner && !this.is_invited) {
var txt = rcmail.gettext('kolab_files.joinsession')
.replace('$user', this.owner_name ? this.owner_name : this.owner);
items.push(item_fn(this.id, txt, 'request'));
}
});
// check the first option
$('input', items[1]).attr('checked', true);
$('div', dialog).html(items);
// if there's only one session and it's owned, skip the dialog
if (!readonly && items.length == 2 && $('input:checked', dialog).parent().is('.owner'))
return select_fn();
}
// no ongoing session, folder is readonly warning
else {
title = rcmail.gettext('kolab_files.editfilerotitle');
height = 150;
$('div', dialog).text(rcmail.gettext('kolab_files.editfilero'));
mainaction = rcmail.gettext('kolab_files.create');
}
buttons[mainaction] = function() { select_fn(this); };
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: title,
buttons: buttons,
button_classes: ['mainaction'],
minHeight: height - 100,
height: height
});
};
// file rename dialog
function kolab_files_file_rename_dialog(file)
{
var dialog = $('#files-file-rename-dialog'),
buttons = {}, name = file_api.file_name(file)
input = $('input[name="name"]', dialog).val(name);
buttons[rcmail.gettext('kolab_files.save')] = function() {
var folder = file_api.file_path(file), name = input.val();
if (!name)
return;
name = folder + file_api.env.directory_separator + name;
if (name != file)
file_api.file_rename(file, name);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.cancel')] = function() {
kolab_dialog_close(this);
};
// Fix submitting form with Enter
$('form', dialog).submit(kolab_dialog_submit_handler);
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.renamefile'),
buttons: buttons,
button_classes: ['mainaction'],
minHeight: 100,
height: 200
});
};
// file creation (or cloning) dialog
function kolab_files_file_create_dialog(file)
{
var buttons = {}, action = file ? 'copy' : 'create',
dialog = $('#files-file-create-dialog'),
type_select = $('select[name="type"]', dialog),
select = $('select[name="parent"]', dialog).html(''),
input = $('input[name="name"]', dialog).val(''),
create_func = function(dialog, editaction) {
var sel, folder = select.val(), type = type_select.val(), name = input.val();
if (!name || !folder)
return;
if (!/\.[a-z0-9]{1,5}$/.test(name)) {
name += '.' + rcmail.env.file_extensions[type];
}
name = folder + file_api.env.directory_separator + name;
// get type of cloned file
if (file) {
if (rcmail.env.file_data)
type = rcmail.env.file_data.type;
else {
sel = rcmail.fileslist.get_selection();
type = $('#rcmrow' + sel[0]).data('type');
}
}
file_api.file_create(name, type, editaction, file);
kolab_dialog_close(dialog);
};
buttons[rcmail.gettext('kolab_files.' + action + 'andedit')] = function() {
create_func(this, true);
};
if (action == 'create') {
buttons[rcmail.gettext('kolab_files.create')] = function() {
create_func(this);
};
type_select.parent('tr').show();
}
else {
input.val(file_api.file_name(file));
type_select.parent('tr').hide();
}
buttons[rcmail.gettext('kolab_files.cancel')] = function() {
kolab_dialog_close(this);
};
// Fix submitting form with Enter
$('form', dialog).submit(kolab_dialog_submit_handler);
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.' + action + 'file'),
buttons: buttons,
button_classes: ['mainaction'],
minHeight: 150,
height: 250
});
// build folder selector
file_api.folder_select_element(select, {writable: true});
};
// file session dialog
function kolab_files_session_dialog(session)
{
var buttons = {},
dialog = $('#files-session-dialog'),
filename = file_api.file_name(session.file),
owner = session.owner_name || session.owner,
title = rcmail.gettext('kolab_files.sessiondialog'),
content = rcmail.gettext('kolab_files.sessiondialogcontent'),
button_classes = ['mainaction'],
join_session = function(id) {
var viewer = file_api.file_type_supported('application/vnd.oasis.opendocument.text', rcmail.env.files_caps);
params = {action: 'edit', session: id};
file_api.file_open('', viewer, params);
};
content = content.replace('$file', filename).replace('$owner', owner);
$('div', dialog).text(content);
if (session.is_owner) {
buttons[rcmail.gettext('kolab_files.open')] = function() {
kolab_dialog_close(this);
join_session(session.id);
};
buttons[rcmail.gettext('kolab_files.close')] = function() {
kolab_dialog_close(this);
file_api.document_delete(session.id);
};
button_classes.push('delete');
}
else if (session.is_invited) {
// @TODO: check if not-accepted and provide "Decline invitation" button
// @TODO: Add "Accept button", add comment field to the dialog
buttons[rcmail.gettext('kolab_files.join')] = function() {
kolab_dialog_close(this);
join_session(session.id);
};
}
else {
buttons[rcmail.gettext('kolab_files.request')] = function() {
kolab_dialog_close(this);
// @TODO: Add comment field to the dialog
manticore.invitation_request({session_id: session.id});
};
}
buttons[rcmail.gettext('kolab_files.cancel')] = function() {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: title,
buttons: buttons,
button_classes: button_classes,
minHeight: 100,
height: 150
});
};
function kolab_dialog_show(content, params, onopen)
{
params = $.extend({
modal: true,
resizable: true,
minWidth: 400,
minHeight: 300,
width: 500,
height: 400
}, params || {});
// dialog close handler
params.close = function(e, ui) {
var elem, stack = rcmail.dialog_stack;
content.appendTo(document.body).hide();
$(this).parent().remove(); // remove dialog
// focus previously focused element (guessed)
stack.pop();
if (stack.length) {
elem = stack[stack.length-1].find('input[type!="hidden"]:not(:hidden):first');
if (!elem.length)
elem = stack[stack.length-1].parent().find('a[role="button"], .ui-dialog-buttonpane button').first();
}
(elem && elem.length ? elem : window).focus();
rcmail.ksearch_blur();
};
// display it as popup
var dialog = rcmail.show_popup_dialog('', params.title, params.buttons, params);
content.appendTo(dialog).show().find('input[type!="hidden"]:not(:hidden):first').focus();
if (onopen) onopen(content);
// save dialog reference, to handle focus when closing one of opened dialogs
if (!rcmail.dialog_stack)
rcmail.dialog_stack = [];
rcmail.dialog_stack.push(dialog);
};
// Handle form submit with Enter key, click first dialog button instead
function kolab_dialog_submit_handler()
{
$(this).parents('.ui-dialog').find('.ui-button').first().click();
return false;
};
// Hides dialog
function kolab_dialog_close(dialog)
{
(rcmail.is_framed() ? window.parent : window).$(dialog).dialog('close');
};
// smart upload button
function kolab_files_upload_input(button)
{
var link = $(button),
file = $('<input>'),
offset = link.offset();
function move_file_input(e) {
file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'});
}
file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5, title: link.attr('title'), tabindex: "-1"})
.change(function() { rcmail.files_upload('#filesuploadform'); })
.click(function() { setTimeout(function() { link.mouseleave(); }, 20); })
// opacity:0 does the trick, display/visibility doesn't work
.css({opacity: 0, cursor: 'pointer', outline: 'none', position: 'absolute'});
// In FF and IE we need to move the browser file-input's button under the cursor
// Thanks to the size attribute above we know the length of the input field
if (bw.mz || bw.ie)
file.css({marginLeft: '-80px'});
// Note: now, I observe problem with cursor style on FF < 4 only
// Need position: relative (Bug #2615)
link.css({overflow: 'hidden', cursor: 'pointer', position: 'relative'})
.mouseenter(function() { this.__active = true; })
// place button under the cursor
.mousemove(function(e) {
if (rcmail.commands['files-upload'] && this.__active)
move_file_input(e);
// move the input away if button is disabled
else
$(this).mouseleave();
})
.mouseleave(function() {
file.css({top: '-10000px', left: '-10000px'});
this.__active = false;
})
.attr('onclick', '') // remove default button action
.click(function(e) {
// forward click if mouse-enter event was missed
if (rcmail.commands['files-upload'] && !this.__active) {
this.__active = true;
move_file_input(e);
file.trigger(e);
}
})
.mouseleave() // initially disable/hide input
.append(file);
};
/***********************************************************/
/********** Main functionality **********/
/***********************************************************/
// for reordering column array (Konqueror workaround)
// and for setting some files/sessions list global variables
function kolab_files_list_coltypes(type)
{
if (!type) type = 'files';
var n, list = rcmail[type + 'list'];
rcmail.env.subject_col = null;
if ((n = $.inArray('name', rcmail.env[type + '_coltypes'])) >= 0) {
rcmail.env.subject_col = n;
list.subject_col = n;
}
list.init_header();
};
function kolab_files_set_list_options(cols, sort_col, sort_order, type)
{
var update = 0, i, idx, name, newcols = [], oldcols = rcmail.env[type + '_coltypes'];
if (sort_col === undefined)
sort_col = rcmail.env[type + '_sort_col'];
if (!sort_order)
sort_order = rcmail.env[type + '_sort_order'];
if (rcmail.env[type + '_sort_col'] != sort_col || rcmail.env[type + '_sort_order'] != sort_order) {
update = 1;
// set table header class
kolab_files_set_list_sorting(sort_col, sort_order, type);
}
if (cols && cols.length) {
// make sure new columns are added at the end of the list
for (i=0; i<oldcols.length; i++) {
name = oldcols[i];
idx = $.inArray(name, cols);
if (idx != -1) {
newcols.push(name);
delete cols[idx];
}
}
for (i=0; i<cols.length; i++)
if (cols[i])
newcols.push(cols[i]);
if (newcols.join() != oldcols.join()) {
update += 2;
oldcols = newcols;
}
}
if (update == 1)
rcmail.command(type + '-list', {sort: sort_col, reverse: sort_order == 'DESC'});
else if (update) {
rcmail.http_post('files/prefs', {
type: type,
kolab_files_list_cols: oldcols,
kolab_files_sort_col: sort_col,
kolab_files_sort_order: sort_order
}, rcmail.set_busy(true, 'loading'));
}
};
function kolab_files_set_list_sorting(sort_col, sort_order, type)
{
// set table header class
var old_col = rcmail.env[type + '_sort_col'],
old_sort = rcmail.env[type + '_sort_order'];
$('#rcm' + old_col).removeClass('sortedASC sortedDESC');
$('#rcm' + sort_col).addClass('sorted' + sort_order);
rcmail.env[type + '_sort_col'] = sort_col;
rcmail.env[type + '_sort_order'] = sort_order;
};
function kolab_files_set_coltypes(list, type)
{
var i, found, name, cols = list.list.tHead.rows[0].cells;
rcmail.env[type + '_coltypes'] = [];
for (i=0; i<cols.length; i++)
if (cols[i].id && cols[i].id.match(/^rcm/)) {
name = cols[i].id.replace(/^rcm/, '');
rcmail.env[type + '_coltypes'].push(name);
}
// if ((found = $.inArray('name', rcmail.env.files_coltypes)) >= 0)
// rcmail.env.subject_col = found;
rcmail.env.subject_col = list.subject_col;
rcmail.http_post('files/prefs', {kolab_files_list_cols: rcmail.env[type + '_coltypes'], type: type});
};
function kolab_files_sessions_list_dblclick(list)
{
rcmail.command('sessions-open');
};
function kolab_files_sessions_list_select(list)
{
var selected = list.selection.length;
rcmail.enable_command('sessions-open', selected == 1);
};
function kolab_files_sessions_list_keypress(list)
{
if (list.modkey == CONTROL_KEY)
return;
if (list.key_pressed == list.ENTER_KEY) {
// use setTimeout(), otherwise the opened dialog will be immediately closed
setTimeout(function() { rcmail.command('sessions-open'); }, 50);
}
// else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
// rcmail.command('sessions-delete');
};
function kolab_files_list_dblclick(list)
{
rcmail.command('files-open');
};
function kolab_files_list_select(list)
{
var selected = list.selection.length;
rcmail.enable_command(rcmail.env.file_commands_all, selected);
rcmail.enable_command(rcmail.env.file_commands, selected == 1);
// reset all-pages-selection
// if (list.selection.length && list.selection.length != list.rowcount)
// rcmail.select_all_mode = false;
if (selected == 1) {
// get file mimetype
var elem = $('tr.selected', list.list),
type = elem.data('type'),
folder = file_api.file_path(elem.data('file'));
rcmail.env.viewer = file_api.file_type_supported(type, rcmail.env.files_caps);
if (!file_api.is_writable(folder))
rcmail.enable_command('files-delete', 'files-rename', false);
}
else
rcmail.env.viewer = 0;
rcmail.enable_command('files-edit', (rcmail.env.viewer & 4) == 4);
rcmail.enable_command('files-open', rcmail.env.viewer);
};
function kolab_files_list_keypress(list)
{
if (list.modkey == CONTROL_KEY)
return;
if (list.key_pressed == list.ENTER_KEY)
rcmail.command('files-open');
else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
rcmail.command('files-delete');
};
function kolab_files_drag_start(e)
{
rcmail.env.drag_target = null;
if (rcmail.folder_list)
rcmail.folder_list.drag_start();
};
function kolab_files_drag_end(e)
{
if (rcmail.folder_list) {
rcmail.folder_list.drag_end();
if (rcmail.env.drag_target) {
var modkey = rcube_event.get_modifier(e),
menu = rcmail.gui_objects.file_dragmenu;
rcmail.fileslist.draglayer.hide();
if (menu && modkey == SHIFT_KEY && rcmail.commands['files-copy']) {
var pos = rcube_event.get_mouse_pos(e);
$(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
return;
}
rcmail.command('files-move', rcmail.env.drag_target);
}
}
};
function kolab_files_drag_move(e)
{
if (rcmail.folder_list) {
var mouse = rcube_event.get_mouse_pos(e);
rcmail.env.drag_target = rcmail.folder_list.intersects(mouse, true);
}
};
function kolab_files_drag_menu_action(command)
{
var menu = rcmail.gui_objects.file_dragmenu;
if (menu)
$(menu).hide();
rcmail.command(command, rcmail.env.drag_target);
};
function kolab_files_selected()
{
var files = [];
$.each(rcmail.fileslist.get_selection(), function(i, v) {
var name, row = $('#rcmrow'+v);
if (row.length == 1 && (name = row.data('file')))
files.push(name);
});
return files;
};
function kolab_files_frame_load(frame)
{
var win = frame.contentWindow,
info = rcmail.env.file_data;
try {
rcmail.file_editor = win.file_editor && win.file_editor.editable ? win.file_editor : null;
}
catch (e) {};
// on edit page switch immediately to edit mode
if (rcmail.file_editor && rcmail.env.action == 'edit')
rcmail.files_edit();
rcmail.enable_command('files-edit', rcmail.file_editor
|| (info && info.viewer && info.viewer.manticore));
rcmail.enable_command('files-print', (rcmail.file_editor && rcmail.file_editor.printable)
|| (info && /^image\//i.test(info.type)));
// detect Print button and check if it can be accessed
try {
if ($('#fileframe').contents().find('#print').length)
rcmail.enable_command('files-print', true);
}
catch(e) {};
};
// activate html5 file drop feature (if browser supports it)
function kolab_files_drag_drop_init(container)
{
if (!window.FormData && !(window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary)) {
return;
}
if (!container.length)
return;
$(document.body).bind('dragover dragleave drop', function(e) {
if (!file_api.is_writable())
return;
e.preventDefault();
container[e.type == 'dragover' ? 'addClass' : 'removeClass']('active');
});
container.bind('dragover dragleave', function(e) {
return kolab_files_drag_hover(e);
})
container.children('div').bind('dragover dragleave', function(e) {
return kolab_files_drag_hover(e);
})
container.get(0).addEventListener('drop', function(e) {
// abort event and reset UI
kolab_files_drag_hover(e);
return file_api.file_drop(e);
}, false);
};
// handler for drag/drop on element
function kolab_files_drag_hover(e)
{
if (!file_api.is_writable())
return;
e.preventDefault();
e.stopPropagation();
var elem = $(e.target);
if (!elem.hasClass('droptarget'))
elem = elem.parents('.droptarget');
elem[e.type == 'dragover' ? 'addClass' : 'removeClass']('hover');
};
// returns localized file size
function kolab_files_file_size(size)
{
var i, units = ['GB', 'MB', 'KB', 'B'];
size = file_api.file_size(size);
for (i = 0; i < units.length; i++)
if (size.toUpperCase().indexOf(units[i]) > 0)
return size.replace(units[i], rcmail.gettext(units[i]));
return size;
};
function kolab_files_progress_str(param)
{
var current, total = file_api.file_size(param.total).toUpperCase();
if (total.indexOf('GB') > 0)
current = parseFloat(param.current/1073741824).toFixed(1);
else if (total.indexOf('MB') > 0)
current = parseFloat(param.current/1048576).toFixed(1);
else if (total.indexOf('KB') > 0)
current = parseInt(param.current/1024);
else
current = param.current;
total = kolab_files_file_size(param.total);
return rcmail.gettext('uploadprogress')
.replace(/\$percent/, param.percent + '%')
.replace(/\$current/, current)
.replace(/\$total/, total);
};
/**********************************************************/
/********* Manticore editor functionality **********/
/**********************************************************/
// Initialize document toolbar functionality
function manticore_init()
{
var info = rcmail.env.file_data;
rcmail.enable_command('document-export', true);
if (info && info.session && info.session.is_owner)
rcmail.enable_command('document-close', 'document-editors', true);
};
// executed on editing session termination
function manticore_close()
{
$('<div>').addClass('popupdialog').attr('role', 'alertdialog')
.html($('<span>').text(rcmail.gettext('kolab_files.sessionterminated')))
.dialog({
resizable: false,
closeOnEscape: true,
dialogClass: 'popupmessage error',
title: rcmail.gettext('kolab_files.sessionterminatedtitle'),
close: function() { window.close(); },
- position: ['center', 'center'],
width: 420,
minHeight: 90
}).show();
return false; // skip Chwala's error message
};
rcube_webmail.prototype.document_save = function()
{
manticore.save(function(data) {
rcmail.enable_command('document-save', false);
});
};
rcube_webmail.prototype.document_export = function(type)
{
manticore.export(type || 'odt');
};
rcube_webmail.prototype.document_editors = function()
{
kolab_files_editors_dialog();
};
// close editing session
rcube_webmail.prototype.document_close = function()
{
// check document "unsaved changes" state and display a warning
if (this.commands['document-save'] && !confirm(this.gettext('kolab_files.unsavedchanges')))
return;
file_api.document_delete(this.env.file_data.session.id);
};
// document editors management dialog
function kolab_files_editors_dialog(session)
{
var items = [], buttons = {},
info = rcmail.env.file_data,
dialog = $('#document-editors-dialog'),
comment = $('#invitation-comment');
if (!info || !info.session || !info.session.is_owner)
return;
// always add the session organizer
items.push(kolab_files_attendee_record(info.session.owner, 'organizer'));
$.each(info.session.invitations || [], function(i, u) {
var record = kolab_files_attendee_record(u.user, u.status, u.user_name);
items.push(record);
info.session.invitations[i].record = record;
});
$('table > tbody', dialog).html(items);
buttons[rcmail.gettext('kolab_files.close')] = function() {
kolab_dialog_close(this);
};
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.manageeditors'),
buttons: buttons,
button_classes: ['mainaction']
});
if (!rcmail.env.editors_dialog) {
rcmail.env.editors_dialog = dialog;
rcmail.init_address_input_events($('#invitation-editor-name'), {action: 'files/autocomplete'});
rcmail.addEventListener('autocomplete_insert', function(e) {
var success = false;
if (e.field.name == 'participant') {
// e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL'
success = kolab_files_add_attendees(e.insert, comment.val());
}
if (e.field && success) {
e.field.value = '';
}
});
$('#invitation-editor-add').click(function() {
var input = $('#invitation-editor-name');
rcmail.ksearch_blur();
if (kolab_files_add_attendees(input.val(), comment.val())) {
input.val('');
}
});
}
};
// add the given list of participants
function kolab_files_add_attendees(names, comment)
{
var i, item, success, email, name, attendees = {}, counter = 0;
names = file_api.explode_quoted_string(names.replace(/,\s*$/, ''), ',');
// parse name/email pairs
for (i = 0; i < names.length; i++) {
email = name = '';
item = $.trim(names[i]);
if (!item.length) {
continue;
} // address in brackets without name (do nothing)
else if (item.match(/^<[^@]+@[^>]+>$/)) {
email = item.replace(/[<>]/g, '');
} // address without brackets and without name (add brackets)
else if (rcube_check_email(item)) {
email = item;
} // address with name
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
email = RegExp.$1;
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
}
if (email) {
attendees[email] = {user: email, name: name};
counter++;
}
else {
alert(rcmail.gettext('noemailwarning'));
}
}
success = counter > 0;
// remove already existing entries
if (counter) {
if (attendees[rcmail.env.file_data.session.owner]) {
delete attendees[this.user];
counter--;
}
$.each(rcmail.env.file_data.session.invitations || [], function() {
if (this.user in attendees) {
delete attendees[this.user];
counter--;
}
});
}
if (counter)
file_api.document_invite(rcmail.env.file_data.session.id, attendees, comment);
return success;
};
function kolab_files_attendee_record(user, status, username)
{
var options = [], select,
type = status ? status.replace(/-.*$/, '') : '',
name = $('<td class="name">').text(user),
buttons = $('<td class="options">'),
state = $('<td class="status">').text(rcmail.gettext('kolab_files.status' + type));
// @todo: accept/decline invitation request
if (type == 'requested' || status == 'accepted-owner' || status == 'declined-owner') {
select = $('<select>').change(function() {
var val = $(this).val(), map = {accepted: 'invitation_accept', declined: 'invitation_decline'};
if (map[val])
manticore[map[val]]({user: user, session_id: rcmail.env.file_data.session.id});
});
if (type == 'requested')
options.push($('<option>').text(rcmail.gettext('kolab_files.statusrequested')).attr('value', 'requested'));
options.push($('<option>').text(rcmail.gettext('kolab_files.statusaccepted')).attr('value', 'accepted'));
options.push($('<option>').text(rcmail.gettext('kolab_files.statusdeclined')).attr('value', 'declined'));
state.html(select.html(options).val(type));
}
// delete button
if (status != 'organizer') {
$('<a>').attr({'class': 'delete', href: '#', title: rcmail.gettext('kolab_files.removeparticipant')})
.click(function() {
file_api.document_cancel(rcmail.env.file_data.session.id, [user]);
})
.appendTo(buttons);
}
if (username && status != 'organizer')
name.html($('<a>').attr({href: 'mailto:' + user, 'class': 'mailtolink'}).text(username))
.click(function(e) { rcmail.command('compose', user, e.target, e); return false; });
return $('<tr>').attr('class', 'invitation' + (type ? ' ' + type : ''))
.append(name).append(state).append(buttons);
};
function manticore_invitation_handler(invitation)
{
// make the "More" link clickable
$('#' + invitation.id).parent('div').click(function() { kolab_files_invitation_dialog(invitation); });
};
function kolab_files_invitation_dialog(invitation)
{
var text, records = [], content = [], buttons = {},
dialog = $('#document-invitation-dialog'),
data_map = {status: 'status', 'changed': 'when', filename: 'file', comment: 'comment'},
record_fn = function(type, label, value) {
records.push($('<tr>').attr('class', type)
.append($('<td class="label">').text(rcmail.gettext('kolab_files.'+ label)))
.append($('<td>').text(value))
);
};
join_session = function() {
var viewer = file_api.file_type_supported('application/vnd.oasis.opendocument.text', rcmail.env.files_caps);
params = {action: 'edit', session: invitation.session_id};
file_api.file_open('', viewer, params);
};
if (!dialog.length)
dialog = $('<div>').attr({id: 'document-invitation-dialog', role: 'dialog', 'aria-hidden': 'true'})
.append($('<div>'))
.appendTo(document.body);
if (!invitation.is_session_owner) {
if (invitation.status == 'invited') {
text = manticore.invitation_msg(invitation);
buttons[rcmail.gettext('kolab_files.join')] = function() {
join_session();
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.accept')] = function() {
manticore.invitation_accept(invitation);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.decline')] = function() {
manticore.invitation_decline(invitation);
kolab_dialog_close(this);
};
}
else if (invitation.status == 'declined-owner') {
// @todo: add option to request for an invitation again?
text = manticore.invitation_msg(invitation);
}
else if (invitation.status == 'accepted-owner') {
text = manticore.invitation_msg(invitation);
buttons[rcmail.gettext('kolab_files.join')] = function() {
join_session();
kolab_dialog_close(this);
};
}
}
else {
if (invitation.status == 'accepted') {
text = manticore.invitation_msg(invitation);
}
else if (invitation.status == 'declined') {
// @todo: add option to invite the user again?
text = manticore.invitation_msg(invitation);
}
else if (invitation.status == 'requested') {
text = manticore.invitation_msg(invitation);
buttons[rcmail.gettext('kolab_files.accept')] = function() {
manticore.invitation_accept(invitation);
kolab_dialog_close(this);
};
buttons[rcmail.gettext('kolab_files.decline')] = function() {
manticore.invitation_decline(invitation);
kolab_dialog_close(this);
};
}
}
if (text) {
$.each(data_map, function(i, label) {
var value = invitation[i];
if (value) {
if (i == 'status')
value = rcmail.gettext('kolab_files.status' + value.replace(/-.*$/, ''));
record_fn(i, label, value);
}
});
content.push($('<div>').text(text));
content.push($('<table class="propform">').html(records));
}
buttons[rcmail.gettext('kolab_files.close')] = function() {
kolab_dialog_close(this);
};
$('div', dialog).html(content);
// show dialog window
kolab_dialog_show(dialog, {
title: rcmail.gettext('kolab_files.invitationtitle').replace('$file', invitation.filename),
buttons: buttons
});
};
/***********************************************************/
/********** Commands **********/
/***********************************************************/
rcube_webmail.prototype.files_sort = function(props)
{
this.files_sort_handler(props, 'files');
};
rcube_webmail.prototype.sessions_sort = function(props)
{
this.files_sort_handler(props, 'sessions');
};
rcube_webmail.prototype.files_sort_handler = function(col, type)
{
var params = {},
c = type == 'files' ? '' : ('_' + type),
sort_order = this.env[type + '_sort_order'],
sort_col = !this.env['kolab_files' + c + '_disabled_sort_col'] ? col : this.env[type + '_sort_col'];
if (!this.env['kolab_files' + c + '_disabled_sort_order'])
sort_order = this.env[type + '_sort_col'] == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
// set table header and update env
kolab_files_set_list_sorting(sort_col, sort_order, type);
this.http_post('files/prefs', {kolab_files_sort_col: sort_col, kolab_files_sort_order: sort_order, type: type});
params.sort = sort_col;
params.reverse = sort_order == 'DESC';
this.command(type + '-list', params);
};
rcube_webmail.prototype.files_search = function()
{
var value = $(this.gui_objects.filesearchbox).val();
if (value)
file_api.file_search(value, $('#search_all_folders').is(':checked'));
else
file_api.file_search_reset();
};
rcube_webmail.prototype.files_search_reset = function()
{
$(this.gui_objects.filesearchbox).val('');
file_api.file_search_reset();
};
rcube_webmail.prototype.files_folder_delete = function()
{
if (confirm(this.get_label('kolab_files.folderdeleteconfirm')))
file_api.folder_delete(file_api.env.folder);
};
rcube_webmail.prototype.files_delete = function()
{
if (!confirm(this.get_label('kolab_files.filedeleteconfirm')))
return;
var files = this.env.file ? [this.env.file] : kolab_files_selected();
file_api.file_delete(files);
};
rcube_webmail.prototype.files_move = function(folder)
{
var files = kolab_files_selected();
file_api.file_move(files, folder);
};
rcube_webmail.prototype.files_copy = function(folder)
{
var files = kolab_files_selected();
file_api.file_copy(files, folder);
};
rcube_webmail.prototype.files_upload = function(form)
{
if (form)
file_api.file_upload(form);
};
rcube_webmail.prototype.files_list = function(param)
{
// just rcmail wrapper, to handle command busy states
file_api.file_list(param);
}
rcube_webmail.prototype.files_list_update = function(head)
{
var list = this.fileslist;
$('thead', list.fixed_header ? list.fixed_header : list.list).html(head);
kolab_files_list_coltypes();
file_api.file_list();
};
rcube_webmail.prototype.sessions_list = function(param)
{
// just rcmail wrapper, to handle command busy states
file_api.session_list(param);
}
rcube_webmail.prototype.sessions_list_update = function(head)
{
var list = this.sessionslist;
$('thead', list.fixed_header ? list.fixed_header : list.list).html(head);
kolab_files_list_coltypes('sessions');
file_api.sessions_list();
};
rcube_webmail.prototype.files_get = function()
{
var files = this.env.file ? [this.env.file] : kolab_files_selected();
if (files.length == 1)
file_api.file_get(files[0], {'force-download': true});
};
rcube_webmail.prototype.files_open = function()
{
var files = kolab_files_selected();
if (files.length == 1)
file_api.file_open(files[0], rcmail.env.viewer);
};
// enable file editor
rcube_webmail.prototype.files_edit = function(session)
{
var files, readonly, sessions, file = this.env.file,
params = {action: 'edit'};
if (!file && !this.env.action) {
files = kolab_files_selected();
if (files.length == 1)
file = files[0];
readonly = !file_api.is_writable(file_api.file_path(file));
}
else {
readonly = !this.env.file_data.writable;
}
// check if the folder is read-only or there are ongoing sessions
// in such cases display dialog for the user to decide what to do
if (!session) {
sessions = file_api.file_sessions(file);
if (sessions.length || readonly) {
kolab_files_file_edit_dialog(file, sessions, readonly);
return;
}
}
else if (session !== true)
params.session = session;
if (this.file_editor && !session) {
this.file_editor.enable();
this.enable_command('files-save', true);
}
else if (this.env.file) {
var viewer = file_api.file_type_supported(this.env.file_data.type, this.env.files_caps);
params.local = true;
file_api.file_open(file, viewer, params);
}
else if (file) {
file_api.file_open(file, this.env.viewer, params);
}
};
// save changes to the file
rcube_webmail.prototype.files_save = function()
{
if (!this.file_editor)
return;
// binary files like ODF need to be updated using FormData
if (this.file_editor.getContentCallback) {
if (!file_api.file_uploader_support())
return;
file_api.req = file_api.set_busy(true, 'saving');
// this.file_editor.disable();
this.file_editor.getContentCallback(function(content, filename) {
file_api.file_uploader([content], {
action: 'file_update',
params: {file: rcmail.env.file, info: 1, token: file_api.env.token},
response_handler: 'file_save_response',
fieldname: 'content',
single: true
});
});
return;
}
var content = this.file_editor.getContent();
file_api.file_save(this.env.file, content);
};
rcube_webmail.prototype.files_print = function()
{
if (this.file_editor && this.file_editor.printable)
this.file_editor.print();
else if (/^image\//i.test(this.env.file_data.type)) {
var frame = $('#fileframe').get(0),
win = frame ? frame.contentWindow : null;
if (win) {
win.focus();
win.print();
}
}
else {
// e.g. Print button in PDF viewer
try {
$('#fileframe').contents().find('#print').click();
}
catch(e) {};
}
};
rcube_webmail.prototype.files_set_quota = function(p)
{
if (p.total && window.file_api) {
p.used *= 1024;
p.total *= 1024;
p.title = file_api.file_size(p.used) + ' / ' + file_api.file_size(p.total)
+ ' (' + p.percent + '%)';
}
p.type = this.env.quota_type;
this.set_quota(p);
};
rcube_webmail.prototype.files_create = function()
{
kolab_files_file_create_dialog();
};
rcube_webmail.prototype.files_rename = function()
{
var files = kolab_files_selected();
kolab_files_file_rename_dialog(files[0]);
};
rcube_webmail.prototype.folder_create = function()
{
kolab_files_folder_create_dialog();
};
rcube_webmail.prototype.folder_rename = function()
{
kolab_files_folder_edit_dialog();
};
rcube_webmail.prototype.folder_mount = function()
{
kolab_files_folder_mount_dialog();
};
// open a session dialog
rcube_webmail.prototype.sessions_open = function()
{
var id = this.sessionslist.get_selection(),
session = id ? file_api.env.sessions_list[id] : null;
if (session)
kolab_files_session_dialog(session);
};
/**********************************************************/
/********* Files API handler **********/
/**********************************************************/
function kolab_files_ui()
{
this.requests = {};
this.uploads = [];
this.workers = {};
/*
// Called on "session expired" session
this.logout = function(response) {};
// called when a request timed out
this.request_timed_out = function() {};
// called on start of the request
this.set_request_time = function() {};
// called on request response
this.update_request_time = function() {};
*/
// set state
this.set_busy = function(a, message)
{
if (this.req)
rcmail.hide_message(this.req);
return rcmail.set_busy(a, message);
};
// displays error message
this.display_message = function(label, type)
{
return rcmail.display_message(this.t(label), type);
};
this.http_error = function(request, status, err, data)
{
rcmail.http_error(request, status, err, data ? data.req_id : null);
};
// check if specified/current folder/view is writable
this.is_writable = function(folder)
{
if (!folder)
folder = this.env.folder;
if (!folder)
return false;
var all_folders = $.extend({}, this.env.folders, this.search_results);
if (!all_folders[folder] || all_folders[folder].readonly || all_folders[folder].virtual)
return false;
return true;
};
// folders list request
this.folder_list = function(params)
{
if (!params)
params = {}
params.permissions = 1;
params.req = this.set_busy(true, 'loading');
this.request('folder_list', this.list_params = params, 'folder_list_response');
};
// folder list response handler
this.folder_list_response = function(response)
{
rcmail.hide_message(this.list_params.req);
if (!this.response(response))
return;
var first, body, rows = [],
list_selector = rcmail.env.folder_list_selector || '#files-folder-list',
search_selector = rcmail.env.folder_search_selector || '#foldersearch',
elem = $(list_selector),
searchbox = $(search_selector),
list = $('<ul class="treelist listing folderlist"></ul>'),
collections = ['audio', 'video', 'image', 'document'];
// try parent window if the list element does not exist
// i.e. called from dialog in parent window
if (!elem.length && window.parent && parent.rcmail) {
body = window.parent.document.body;
elem = $(list_selector, body);
searchbox = $(search_selector, body);
}
if (elem.data('no-collections') == true)
collections = [];
elem.html('');
this.env.folders = this.folder_list_parse(response.result && response.result.list ? response.result.list : response.result);
$.each(this.env.folders, function(i, f) {
var row;
if (row = file_api.folder_list_row(i, f)) {
if (!first)
first = i;
rows.push(row);
}
});
// add virtual collections
$.each(collections, function(i, n) {
var row = $('<li class="mailbox collection ' + n + '"></li>');
row.attr('id', 'rcmli' + rcmail.html_identifier_encode('folder-collection-' + n))
.append($('<a class="name"></a>').text(rcmail.gettext('kolab_files.collection_' + n)))
rows.push(row);
});
// add Sessions entry
if (rcmail.task == 'files' && !rcmail.env.action && rcmail.env.files_caps && rcmail.env.files_caps.MANTICORE) {
rows.push($('<li class="mailbox collection sessions"></li>')
.attr('id', 'rcmli' + rcmail.html_identifier_encode('folder-collection-sessions'))
.append($('<a class="name"></a>').text(rcmail.gettext('kolab_files.sessions')))
);
}
list.append(rows).appendTo(elem)
.on('click', 'a.subscription', function(e) {
return file_api.folder_list_subscription_button_click(this);
});
if (rcmail.folder_list) {
rcmail.folder_list.reset();
this.search_results_widget = null;
}
// init treelist widget
rcmail.folder_list = new rcube_treelist_widget(list, {
selectable: true,
id_prefix: 'rcmli',
parent_focus: true,
searchbox: searchbox,
id_encode: rcmail.html_identifier_encode,
id_decode: rcmail.html_identifier_decode,
check_droptarget: function(node) {
return !node.virtual
&& node.id != file_api.env.folder
&& $.inArray('readonly', node.classes) == -1
&& $.inArray('collection', node.classes) == -1;
}
});
rcmail.folder_list
.addEventListener('collapse', function(node) { file_api.folder_collapsed(node); })
.addEventListener('expand', function(node) { file_api.folder_collapsed(node); })
.addEventListener('beforeselect', function(node) { return !rcmail.busy; })
.addEventListener('search', function(search) { file_api.folder_search(search); })
.addEventListener('select', function(node) {
if (file_api.search_results_widget)
file_api.search_results_widget.select();
file_api.folder_select(node.id);
});
// select first folder?
if (response.result.auth_errors) { }
else if (this.env.folder)
rcmail.folder_list.select(this.env.folder);
else if (this.env.collection)
rcmail.folder_list.select('folder-collection-' + this.env.collection);
else if (first)
rcmail.folder_list.select(first);
rcmail.enable_command('files-create', true);
// add tree icons
// this.folder_list_tree(this.env.folders);
// handle authentication errors on external sources
if (elem.length)
this.folder_list_auth_errors(response.result);
};
this.folder_select = function(folder)
{
if (rcmail.busy)
return;
var is_collection = folder.match(/^folder-collection-(.*)$/),
collection = RegExp.$1 || null;
if (collection == 'sessions') {
rcmail.enable_command('files-list', 'files-folder-delete', 'folder-rename', 'files-upload', false);
this.sessions_list();
return;
}
if (is_collection)
folder = null;
// search-reset can re-select the same folder, skip
if (this.env.folder == folder && this.env.collection == collection)
return;
this.env.folder = folder;
this.env.collection = collection;
rcmail.enable_command('files-list', true);
rcmail.enable_command('files-folder-delete', 'folder-rename', !is_collection);
rcmail.enable_command('files-upload', !is_collection && this.is_writable());
rcmail.command('files-list', is_collection ? {collection: collection} : {folder: folder});
this.quota();
};
this.folder_unselect = function()
{
rcmail.folder_list.select();
this.env.folder = null;
this.env.collection = null;
rcmail.enable_command('files-folder-delete', 'files-upload', false);
};
this.folder_collapsed = function(node)
{
var prefname = 'kolab_files_collapsed_folders',
old = rcmail.env[prefname],
entry = '&' + urlencode(node.id) + '&';
if (node.collapsed) {
rcmail.env[prefname] = rcmail.env[prefname] + entry;
// select the folder if one of its childs is currently selected
// don't select if it's virtual (#1488346)
if (!node.virtual && this.env.folder && this.env.folder.startsWith(node.id + '/')) {
rcmail.folder_list.select(node.id);
}
}
else {
rcmail.env[prefname] = rcmail.env[prefname].replace(entry, '');
}
if (old !== rcmail.env[prefname] && (!rcmail.fileslist || !rcmail.fileslist.drag_active))
rcmail.command('save-pref', {name: prefname, value: rcmail.env[prefname]});
};
this.folder_list_row = function(i, folder, parent)
{
var toggle, sublist, collapsed, parent, parent_name, classes = ['mailbox'],
row = $('<li>'),
id = 'rcmli' + rcmail.html_identifier_encode(i);
row.attr('id', id).append($('<a class="name">').text(folder.name));
if (folder.virtual)
classes.push('virtual');
else {
if (folder.subscribed !== undefined)
row.append(this.folder_list_subscription_button(folder.subscribed));
if (folder.readonly)
classes.push('readonly');
}
row.addClass(classes.join(' '));
folder.ref = row;
if (folder.depth) {
// find parent folder
parent_name = i.replace(/\/[^/]+$/, '');
if (!parent)
parent = $(this.env.folders[parent_name].ref);
toggle = $('div.treetoggle', parent);
sublist = $('> ul', parent);
if (!toggle.length) {
collapsed = rcmail.env.kolab_files_collapsed_folders.indexOf('&' + urlencode(parent_name) + '&') > -1;
toggle = $('<div>').attr('class', 'treetoggle' + (collapsed ? ' collapsed' : ' expanded'))
.html('&nbsp;').appendTo(parent);
sublist = $('<ul>').attr({role: 'group'}).appendTo(parent);
if (collapsed)
sublist.hide();
}
sublist.append(row);
}
else {
return row;
}
};
// create subscription button element
this.folder_list_subscription_button = function(subscribed)
{
return $('<a>').attr({
title: rcmail.gettext('kolab_files.listpermanent'),
'class': 'subscription' + (subscribed ? ' subscribed' : ''),
'aria-checked': subscribed,
role: 'checkbox'
});
};
// subscription button handler
this.folder_list_subscription_button_click = function(elem)
{
var folder = $(elem).parent('li').prop('id').replace(/^rcmli/, ''),
selected = $(elem).hasClass('subscribed');
folder = folder.replace(/--xsR$/, ''); // this might be a search result
folder = rcmail.html_identifier_decode(folder);
file_api['folder_' + (selected ? 'unsubscribe' : 'subscribe')](folder);
return false;
};
// sets subscription button status
this.folder_list_subscription_state = function(elem, status)
{
$(elem).children('a.subscription')
.prop('aria-checked', status)[status ? 'addClass' : 'removeClass']('subscribed');
};
// Folder searching handler (for unsubscribed folders)
this.folder_search = function(search)
{
// hide search results
if (this.search_results_widget) {
this.search_results_container.hide();
this.search_results_widget.reset();
}
this.search_results = {};
// send search request to the server
if (search.query && search.execute) {
// cancel previous search request
if (this.listsearch_request) {
this.listsearch_request.abort();
this.listsearch_request = null;
}
var params = $.extend({search: search.query, unsubscribed: 1}, this.list_params);
this.req = this.set_busy(true, rcmail.gettext('searching'));
this.listsearch_request = this.request('folder_list', params, 'folder_search_response');
}
else if (!search.query) {
if (this.listsearch_request) {
this.listsearch_request.abort();
this.listsearch_request = null;
}
// any subscription changed, make sure the newly added records
// are listed before collections not after
if (this.folder_subscribe) {
var r, last, move = [], rows = $(rcmail.folder_list.container).children('li');
if (rows.length && !$(rows[rows.length-1]).hasClass('collection')) {
// collect all folders to move
while (rows.length--) {
r = $(rows[rows.length]);
if (r.hasClass('collection'))
last = r;
else if (last)
break;
else
move.push(r);
}
if (last)
$.each(move, function() {
this.remove();
last.before(this);
});
}
}
}
};
// folder search response handler
this.folder_search_response = function(response)
{
if (!this.response(response))
return;
var folders = response.result && response.result.list ? response.result.list : response.result;
if (!folders.length)
return;
folders = this.folder_list_parse(folders, 10000, false);
if (!this.search_results_widget) {
var list = rcmail.folder_list.container,
title = rcmail.gettext('kolab_files.additionalfolders'),
list_id = list.attr('id') || '0';
this.search_results_container = $('<div class="searchresults"></div>')
.append($('<h2 class="boxtitle" id="st:' + list_id + '"></h2>').text(title))
.insertAfter(list);
this.search_results_widget = new rcube_treelist_widget('<ul>', {
id_prefix: 'rcmli',
id_encode: rcmail.html_identifier_encode,
id_decode: rcmail.html_identifier_decode,
selectable: true
});
this.search_results_widget
.addEventListener('beforeselect', function(node) { return !rcmail.busy; })
.addEventListener('select', function(node) {
rcmail.folder_list.select();
file_api.folder_select(node.id);
});
this.search_results_widget.container
// copy classes from main list
.addClass(list.attr('class')).attr('aria-labelledby', 'st:' + list_id)
.appendTo(this.search_results_container)
.on('click', 'a.subscription', function(e) {
return file_api.folder_list_subscription_button_click(this);
});
}
// add results to the list
$.each(folders, function(i, folder) {
var node, separator = file_api.env.directory_separator,
path = i.split(separator),
classes = ['mailbox'],
html = [$('<a>').text(folder.name)];
if (!folder.virtual) {
// add subscription button
html.push(file_api.folder_list_subscription_button(false));
if (folder.readonly)
classes.push('readonly');
}
path.pop();
file_api.search_results_widget.insert({
id: i,
classes: classes,
text: folder.name,
html: html,
collapsed: false,
virtual: folder.virtual
}, path.length ? path.join(separator) : null);
});
this.search_results = folders;
this.search_results_container.show();
};
// folder subscribe request
this.folder_subscribe = function(folder)
{
this.env.folder_subscribe = folder;
this.req = this.set_busy(true, 'foldersubscribing');
this.request('folder_subscribe', {folder: folder}, 'folder_subscribe_response');
}
// folder subscribe response handler
this.folder_subscribe_response = function(response)
{
if (!this.response(response))
return;
this.display_message('foldersubscribed', 'confirmation');
var item, node = rcmail.folder_list.get_item(this.env.folder_subscribe);
if (this.search_results && this.search_results[this.env.folder_subscribe]) {
item = this.search_results_widget.get_item(this.env.folder_subscribe);
this.folder_list_subscription_state(item, true);
if (item = $(item).attr('id'))
this.folder_list_subscription_state($('#' + item.replace(/--xsR$/, '')), true);
}
// search result, move from search to main list widget
if (!node && this.search_results && this.search_results[this.env.folder_subscribe]) {
var i, html, dir, folder, separator = this.env.directory_separator,
path = this.env.folder_subscribe.split(separator);
// add all folders in a path to the main list if needed
// including the subscribed folder
for (i=0; i<path.length; i++) {
dir = path.slice(0, i + 1).join(separator);
node = rcmail.folder_list.get_node(dir);
if (!node) {
node = this.search_results_widget.get_node(dir);
if (!node) {
// sanity check
return;
}
if (i == path.length - 1) {
item = this.search_results_widget.get_item(dir);
this.folder_list_subscription_state(item, true);
}
folder = this.search_results[dir];
html = [$('<a>').text(folder.name)];
if (!folder.virtual)
html.push(this.folder_list_subscription_button(true));
node.html = html;
delete node.children;
rcmail.folder_list.insert(node, i > 0 ? path.slice(0, i).join(separator) : null);
// we're in search result, so there will be two records,
// add subscription button to the visible one, it was not cloned
if (!folder.virtual) {
node = rcmail.folder_list.get_item(dir);
$(node).append(file_api.folder_list_subscription_button(true));
}
this.env.folders[dir] = folder;
}
}
// now remove them from the search widget
while (path.length) {
dir = path.join(separator);
node = this.search_results_widget.get_item(dir);
if ($('ul[role="group"] > li', node).length)
break;
this.search_results_widget.remove(dir);
path.pop();
}
node = null;
}
if (node)
this.folder_list_subscription_state(node, true);
this.env.folders[this.env.folder_subscribe].subscribed = true;
};
// folder unsubscribe request
this.folder_unsubscribe = function(folder)
{
this.env.folder_subscribe = folder;
this.req = this.set_busy(true, 'folderunsubscribing');
this.request('folder_unsubscribe', {folder: folder}, 'folder_unsubscribe_response');
}
// folder unsubscribe response handler
this.folder_unsubscribe_response = function(response)
{
if (!this.response(response))
return;
this.display_message('folderunsubscribed', 'confirmation');
var folder = this.env.folders[this.env.folder_subscribe],
node = rcmail.folder_list.get_item(this.env.folder_subscribe);
if (this.search_results && this.search_results[this.env.folder_subscribe]) {
item = this.search_results_widget.get_item(this.env.folder_subscribe);
if (item) {
this.folder_list_subscription_state(item, false);
item = $('#' + $(item).attr('id').replace(/--xsR$/, ''));
}
else
item = $('#rcmli' + rcmail.html_identifier_encode(this.env.folder_subscribe), rcmail.folder_list.container);
this.folder_list_subscription_state(item, false);
}
this.folder_list_subscription_state(node, false);
folder.subscribed = false;
};
// folder create request
this.folder_create = function(folder)
{
this.req = this.set_busy(true, 'kolab_files.foldercreating');
this.request('folder_create', {folder: folder}, 'folder_create_response');
};
// folder create response handler
this.folder_create_response = function(response)
{
if (!this.response(response))
return;
this.display_message('kolab_files.foldercreatenotice', 'confirmation');
// refresh folders list
this.folder_list();
};
// folder rename request
this.folder_rename = function(folder, new_name)
{
if (folder == new_name)
return;
this.env.folder_rename = new_name;
this.req = this.set_busy(true, 'kolab_files.folderupdating');
this.request('folder_move', {folder: folder, 'new': new_name}, 'folder_rename_response');
};
// folder create response handler
this.folder_rename_response = function(response)
{
if (!this.response(response))
return;
this.display_message('kolab_files.folderupdatenotice', 'confirmation');
// refresh folders and files list
this.env.folder = this.env.folder_rename;
this.folder_list();
};
// folder mount (external storage) request
this.folder_mount = function(data)
{
this.req = this.set_busy(true, 'kolab_files.foldermounting');
this.request('folder_create', data, 'folder_mount_response');
};
// folder create response handler
this.folder_mount_response = function(response)
{
if (!this.response(response))
return;
this.display_message('kolab_files.foldermountnotice', 'confirmation');
// refresh folders list
this.folder_list();
};
// folder delete request
this.folder_delete = function(folder)
{
this.req = this.set_busy(true, 'kolab_files.folderdeleting');
this.request('folder_delete', {folder: folder}, 'folder_delete_response');
};
// folder delete response handler
this.folder_delete_response = function(response)
{
if (!this.response(response))
return;
this.env.folder = null;
rcmail.enable_command('files-folder-delete', 'folder-rename', 'files-list', false);
this.display_message('kolab_files.folderdeletenotice', 'confirmation');
// refresh folders list
this.folder_list();
this.quota();
};
// quota request
this.quota = function()
{
if (rcmail.env.files_quota)
this.request('quota', {folder: this.env.folder}, 'quota_response');
};
// quota response handler
this.quota_response = function(response)
{
if (!this.response(response))
return;
rcmail.files_set_quota(response.result);
};
// List of sessions
this.sessions_list = function(params)
{
if (!rcmail.gui_objects.sessionslist)
return;
if (!params)
params = {};
if (rcmail.gui_objects.fileslist) {
$(rcmail.gui_objects.fileslist).parent().hide();
$(rcmail.gui_objects.sessionslist).parent().show();
rcmail.fileslist.clear();
}
this.env.folder = null;
this.env.collection = null;
// empty the list
this.env.sessions_list = {};
rcmail.sessionslist.clear(true);
rcmail.enable_command(rcmail.env.file_commands, false);
rcmail.enable_command(rcmail.env.file_commands_all, false);
params.req_id = this.set_busy(true, 'loading');
this.requests[params.req_id] = this.request('sessions', params, 'sessions_list_response');
};
// file list response handler
this.sessions_list_response = function(response)
{
if (response.req_id)
rcmail.hide_message(response.req_id);
if (!this.response(response))
return;
$.each(response.result, function(sess_id, data) {
var row = file_api.session_list_row(sess_id, data);
rcmail.sessionslist.insert_row(row);
response.result[sess_id].row = row;
});
this.env.sessions_list = response.result;
rcmail.sessionslist.resize();
};
// Files list
this.file_list = function(params)
{
if (!rcmail.gui_objects.fileslist)
return;
if (!params)
params = {};
// reset all pending list requests
$.each(this.requests, function(i, v) {
v.abort();
rcmail.hide_message(i);
});
// reset folder_info workers
$.each(this.workers, function(i, v) {
clearTimeout(v);
});
this.workers = {};
this.requests = {};
if (params.all_folders) {
params.collection = null;
params.folder = null;
this.folder_unselect();
}
if (params.collection == undefined)
params.collection = this.env.collection;
if (params.folder == undefined)
params.folder = this.env.folder;
if (params.sort == undefined)
params.sort = this.env.files_sort_col;
if (params.reverse == undefined)
params.reverse = this.env.files_sort_reverse;
if (params.search == undefined)
params.search = this.env.search;
this.env.folder = params.folder;
this.env.collection = params.collection;
this.env.files_sort_col = params.sort;
this.env.files_sort_reverse = params.reverse;
rcmail.enable_command(rcmail.env.file_commands, false);
rcmail.enable_command(rcmail.env.file_commands_all, false);
if (rcmail.gui_objects.sessionslist) {
$(rcmail.gui_objects.sessionslist).parent().hide();
$(rcmail.gui_objects.fileslist).parent().show();
rcmail.sessionslist.clear(true)
}
// empty the list
this.env.file_list = [];
rcmail.fileslist.clear(true);
// request
if (params.collection || params.all_folders)
this.file_list_loop(params);
else if (this.env.folder) {
params.req_id = this.set_busy(true, 'loading');
this.requests[params.req_id] = this.request('file_list', params, 'file_list_response');
}
};
// file list response handler
this.file_list_response = function(response)
{
if (response.req_id)
rcmail.hide_message(response.req_id);
if (!this.response(response))
return;
var i = 0, list = [];
$.each(response.result, function(key, data) {
var row = file_api.file_list_row(key, data, ++i);
rcmail.fileslist.insert_row(row);
data.row = row;
data.filename = key;
list.push(data);
});
this.env.file_list = list;
rcmail.fileslist.resize();
// update document sessions info of this folder
if (list && list.length)
this.request('folder_info', {folder: this.file_path(list[0].filename), sessions: 1}, 'folder_info_response');
};
// call file_list request for every folder (used for search and virt. collections)
this.file_list_loop = function(params)
{
var i, folders = [], limit = Math.max(this.env.search_threads || 1, 1);
if (params.collection) {
if (!params.search)
params.search = {};
params.search['class'] = params.collection;
delete params['collection'];
}
delete params['all_folders'];
$.each(this.env.folders, function(i, f) {
if (!f.virtual)
folders.push(i);
});
this.env.folders_loop = folders;
this.env.folders_loop_params = params;
this.env.folders_loop_lock = false;
for (i=0; i<folders.length && i<limit; i++) {
params.req_id = this.set_busy(true, 'loading');
params.folder = folders.shift();
this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response');
}
};
// file list response handler for loop'ed request
this.file_list_loop_response = function(response)
{
var i, folders = this.env.folders_loop,
params = this.env.folders_loop_params,
limit = Math.max(this.env.search_threads || 1, 1),
valid = this.response(response);
if (response.req_id)
rcmail.hide_message(response.req_id);
for (i=0; i<folders.length && i<limit; i++) {
params.req_id = this.set_busy(true, 'loading');
params.folder = folders.shift();
this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response');
}
rcmail.fileslist.resize();
if (!valid)
return;
this.file_list_loop_result_add(response.result);
};
// add files from list request to the table (with sorting)
this.file_list_loop_result_add = function(result)
{
// chack if result (hash-array) is empty
if (!object_is_empty(result))
return;
if (this.env.folders_loop_lock) {
setTimeout(function() { file_api.file_list_loop_result_add(result); }, 100);
return;
}
// lock table, other list responses will wait
this.env.folders_loop_lock = true;
var n, i, len, elem, row, folder, list = [],
index = this.env.file_list.length,
table = rcmail.fileslist;
for (n=0, len=index; n<len; n++) {
elem = this.env.file_list[n];
for (i in result) {
if (this.sort_compare(elem, result[i]) < 0)
break;
row = this.file_list_row(i, result[i], ++index);
table.insert_row(row, elem.row);
result[i].row = row;
result[i].filename = i;
list.push(result[i]);
if (!folder)
folder = this.file_path(i);
delete result[i];
}
list.push(elem);
}
// add the rest of rows
$.each(result, function(key, data) {
var row = file_api.file_list_row(key, data, ++index);
table.insert_row(row);
result[key].row = row;
result[key].filename = key;
list.push(result[key]);
if (!folder)
folder = file_api.file_path(key);
});
this.env.file_list = list;
this.env.folders_loop_lock = false;
// update document sessions info of this folder
if (folder)
this.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response');
};
// sort files list (without API request)
this.file_list_sort = function(col, reverse)
{
var n, len, list = this.env.file_list,
table = $('#fileslist'), tbody = $('<tbody>', table);
this.env.sort_col = col;
this.env.sort_reverse = reverse;
if (!list || !list.length)
return;
// sort the list
list.sort(function (a, b) {
return file_api.sort_compare(a, b);
});
// add rows to the new body
for (n=0, len=list.length; n<len; n++) {
tbody.append(list[n].row);
}
// replace table bodies
$('tbody', table).replaceWith(tbody);
};
// Files list record
this.file_list_row = function(file, data, index)
{
var c, col, row = '';
for (c in rcmail.env.files_coltypes) {
c = rcmail.env.files_coltypes[c];
if (c == 'name')
col = '<td class="name filename ' + this.file_type_class(data.type) + '">'
+ '<span>' + escapeHTML(data.name) + '</span></td>';
else if (c == 'mtime')
col = '<td class="mtime">' + data.mtime + '</td>';
else if (c == 'size')
col = '<td class="size">' + this.file_size(data.size) + '</td>';
else if (c == 'options')
col = '<td class="options"><span></span></td>';
else
col = '<td class="' + c + '"></td>';
row += col;
}
row = $('<tr>')
.html(row)
.attr({id: 'rcmrow' + index, 'data-file': file, 'data-type': data.type});
// collection (or search) lists files from all folders
// display file name with full path as title
if (!this.env.folder)
$('td.name span', row).attr('title', file);
return row.get(0);
};
// Sessions list record
this.session_list_row = function(id, data)
{
var c, col, row = '', classes = ['session'];
for (c in rcmail.env.sessions_coltypes) {
c = rcmail.env.sessions_coltypes[c];
if (c == 'name')
col = '<td class="name filename ' + this.file_type_class(data.type) + '">'
+ '<span>' + escapeHTML(file_api.file_name(data.file)) + '</span></td>';
/*
else if (c == 'mtime')
col = '<td class="mtime">' + data.mtime + '</td>';
else if (c == 'size')
col = '<td class="size">' + this.file_size(data.size) + '</td>';
*/
else if (c == 'owner')
col = '<td class="owner">' + escapeHTML(data.owner_name || data.owner) + '</td>';
else if (c == 'options')
col = '<td class="options"><span></span></td>';
row += col;
}
if (data.is_owner)
classes.push('owner');
else if (data.is_invited)
classes.push('invited');
row = $('<tr>')
.html(row)
.attr({id: 'rcmrow' + id, 'class': classes.join(' ')});
return row.get(0);
};
this.file_search = function(value, all_folders)
{
if (value) {
this.env.search = {name: value};
rcmail.command('files-list', {search: this.env.search, all_folders: all_folders});
}
else
this.search_reset();
};
this.file_search_reset = function()
{
if (this.env.search) {
this.env.search = null;
rcmail.command('files-list');
}
};
// handler for folder info response
this.folder_info_response = function(response)
{
if (!this.response(response) || !response.result)
return;
var folder = response.result.folder,
prefix = folder + file_api.env.directory_separator;
if (response.result.sessions)
this.sessions[folder] = response.result.sessions;
// update files list with document session info
$.each(file_api.env.file_list || [], function(i, file) {
// skip files from a different folder (in multi-folder listing)
if (file.filename.indexOf(prefix) !== 0)
return;
var classes = [];
if ($(file.row).is('.selected'))
classes.push('selected');
$.each(response.result.sessions || [], function(session_id, session) {
if (file.filename == session.file) {
if ($.inArray('session', classes) < 0)
classes.push('session');
if (session.is_owner && $.inArray('owner', classes) < 0)
classes.push('owner');
else if (session.is_invited && $.inArray('invited', classes) < 0)
classes.push('invited');
}
});
$(file.row).attr('class', classes.join(' '));
});
// refresh sessions info in time intervals
if (rcmail.env.files_caps && rcmail.env.files_caps.MANTICORE && (rcmail.fileslist || rcmail.env.file))
this.workers[folder] = setTimeout(function() {
file_api.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response');
}, (rcmail.env.files_interval || 60) * 1000);
};
this.file_get = function(file, params)
{
if (!params)
params = {};
params.token = this.env.token;
params.file = file;
rcmail.redirect(this.env.url + this.url('file_get', params));
};
// file(s) delete request
this.file_delete = function(files)
{
this.req = this.set_busy(true, 'kolab_files.filedeleting');
this.request('file_delete', {file: files}, 'file_delete_response');
};
// file(s) delete response handler
this.file_delete_response = function(response)
{
if (!this.response(response))
return;
var rco, dir, self = this;
this.display_message('kolab_files.filedeletenotice', 'confirmation');
if (rcmail.env.file) {
rco = rcmail.opener();
dir = this.file_path(rcmail.env.file);
// check if opener window contains files list, if not we can just close current window
if (rco && rco.fileslist && (opener.file_api.env.folder == dir || !opener.file_api.env.folder))
self = opener.file_api;
else
window.close();
}
// @TODO: consider list modification "in-place" instead of full reload
self.file_list();
self.quota();
if (rcmail.env.file)
window.close();
};
// file(s) move request
this.file_move = function(files, folder)
{
if (!files || !files.length || !folder)
return;
var count = 0, list = {};
$.each(files, function(i, v) {
var name = folder + file_api.env.directory_separator + file_api.file_name(v);
if (name != v) {
list[v] = name;
count++;
}
});
if (!count)
return;
this.req = this.set_busy(true, 'kolab_files.filemoving');
this.request('file_move', {file: list}, 'file_move_response');
};
// file(s) move response handler
this.file_move_response = function(response)
{
if (!this.response(response))
return;
if (response.result && response.result.already_exist && response.result.already_exist.length)
this.file_move_ask_user(response.result.already_exist, true);
else {
this.display_message('kolab_files.filemovenotice', 'confirmation');
this.file_list();
}
};
// file(s) copy request
this.file_copy = function(files, folder)
{
if (!files || !files.length || !folder)
return;
var count = 0, list = {};
$.each(files, function(i, v) {
var name = folder + file_api.env.directory_separator + file_api.file_name(v);
if (name != v) {
list[v] = name;
count++;
}
});
if (!count)
return;
this.req = this.set_busy(true, 'kolab_files.filecopying');
this.request('file_copy', {file: list}, 'file_copy_response');
};
// file(s) copy response handler
this.file_copy_response = function(response)
{
if (!this.response(response))
return;
if (response.result && response.result.already_exist && response.result.already_exist.length)
this.file_move_ask_user(response.result.already_exist);
else {
this.display_message('kolab_files.filecopynotice', 'confirmation');
this.quota();
}
};
// when file move/copy operation returns file-exists error
// this displays a dialog where user can decide to skip
// or overwrite destination file(s)
this.file_move_ask_user = function(list, move)
{
var file = list[0], buttons = {},
text = rcmail.gettext('kolab_files.filemoveconfirm').replace('$file', file.dst)
dialog = $('<div></div>');
buttons[rcmail.gettext('kolab_files.fileoverwrite')] = function() {
var file = list.shift(), f = {},
action = move ? 'file_move' : 'file_copy';
f[file.src] = file.dst;
file_api.file_move_ask_list = list;
file_api.file_move_ask_mode = move;
dialog.dialog('destroy').remove();
file_api.req = file_api.set_busy(true, move ? 'kolab_files.filemoving' : 'kolab_files.filecopying');
file_api.request(action, {file: f, overwrite: 1}, 'file_move_ask_user_response');
};
if (list.length > 1)
buttons[rcmail.gettext('kolab_files.fileoverwriteall')] = function() {
var f = {}, action = move ? 'file_move' : 'file_copy';
$.each(list, function() { f[this.src] = this.dst; });
dialog.dialog('destroy').remove();
file_api.req = file_api.set_busy(true, move ? 'kolab_files.filemoving' : 'kolab_files.filecopying');
file_api.request(action, {file: f, overwrite: 1}, action + '_response');
};
var skip_func = function() {
list.shift();
dialog.dialog('destroy').remove();
if (list.length)
file_api.file_move_ask_user(list, move);
else if (move)
file_api.file_list();
};
buttons[rcmail.gettext('kolab_files.fileskip')] = skip_func;
if (list.length > 1)
buttons[rcmail.gettext('kolab_files.fileskipall')] = function() {
dialog.dialog('destroy').remove();
if (move)
file_api.file_list();
};
// open jquery UI dialog
kolab_dialog_show(dialog.html(text), {
close: skip_func,
buttons: buttons,
minWidth: 400,
width: 400
});
};
// file move (with overwrite) response handler
this.file_move_ask_user_response = function(response)
{
var move = this.file_move_ask_mode, list = this.file_move_ask_list;
this.response(response);
if (list && list.length)
this.file_move_ask_user(list, mode);
else {
this.display_message('kolab_files.file' + (move ? 'move' : 'copy') + 'notice', 'confirmation');
if (move)
this.file_list();
}
};
// file(s) create (or clone) request
this.file_create = function(file, type, edit, cloneof)
{
this.file_create_edit_file = edit ? file : null;
this.file_create_edit_type = edit ? type : null;
this.file_create_folder = this.file_path(file);
if (cloneof) {
this.req = this.set_busy(true, 'kolab_files.filecopying');
this.request('file_copy', {file: cloneof, 'new': file}, 'file_create_response');
}
else {
this.req = this.set_busy(true, 'kolab_files.filecreating');
this.request('file_create', {file: file, 'content-type': type, content: ''}, 'file_create_response');
}
};
// file(s) create response handler
this.file_create_response = function(response)
{
if (!this.response(response))
return;
// @TODO: we could update metadata instead
if (this.file_create_folder == this.env.folder)
this.file_list();
// open the file for editing if editable
if (this.file_create_edit_file) {
var viewer = this.file_type_supported(this.file_create_edit_type, rcmail.env.files_caps);
this.file_open(this.file_create_edit_file, viewer, {action: 'edit'});
}
};
// file(s) rename request
this.file_rename = function(oldfile, newfile)
{
this.req = this.set_busy(true, 'kolab_files.fileupdating');
this.request('file_move', {file: oldfile, 'new': newfile}, 'file_rename_response');
};
// file(s) move response handler
this.file_rename_response = function(response)
{
if (!this.response(response))
return;
// @TODO: we could update metadata instead
this.file_list();
};
// file upload request
this.file_upload = function(form)
{
var form = $(form),
field = $('input[type=file]', form).get(0),
files = field.files ? field.files.length : field.value ? 1 : 0;
if (!files || !this.file_upload_size_check(field.files))
return;
// submit form and read server response
this.file_upload_form(form, 'file_upload', function(event) {
var doc, response;
try {
doc = this.contentDocument ? this.contentDocument : this.contentWindow.document;
response = doc.body.innerHTML;
// response may be wrapped in <pre> tag
if (response.slice(0, 5).toLowerCase() == '<pre>' && response.slice(-6).toLowerCase() == '</pre>') {
response = doc.body.firstChild.firstChild.nodeValue;
}
response = eval('(' + response + ')');
}
catch (err) {
response = {status: 'ERROR'};
}
file_api.file_upload_progress_stop(event.data.ts);
// refresh the list on upload success
file_api.file_upload_response(response);
});
};
// refresh the list on upload success
this.file_upload_response = function(response)
{
if (this.response_parse(response)) {
this.file_list();
this.quota();
}
};
// check upload max size
this.file_upload_size_check = function(files)
{
var i, size = 0, maxsize = rcmail.env.files_max_upload;
if (maxsize && files) {
for (i=0; i < files.length; i++)
size += files[i].size || files[i].fileSize;
if (size > maxsize) {
alert(rcmail.get_label('kolab_files.uploadsizeerror').replace('$size', kolab_files_file_size(maxsize)));
return false;
}
}
return true;
};
// post the given form to a hidden iframe
this.file_upload_form = function(form, action, onload)
{
var ts = new Date().getTime(),
frame_name = 'fileupload' + ts;
// upload progress support
if (rcmail.env.files_progress_name) {
var fname = rcmail.env.files_progress_name,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(ts);
this.file_upload_progress(ts, true);
}
rcmail.display_progress({name: ts});
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe id="'+frame_name+'" name="'+frame_name+'"'
+ ' src="' + rcmail.assets_path('program/resources/blank.gif') + '"'
+ ' style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd', html);
}
// for standards-compliant browsers
else
$('<iframe>')
.attr({name: frame_name, id: frame_name})
.css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
.appendTo(document.body);
// handle upload errors, parsing iframe content in onload
$('#'+frame_name).on('load', {ts:ts}, onload);
$(form).attr({
target: frame_name,
action: this.env.url + this.url(action, {folder: this.env.folder, token: this.env.token}),
method: 'POST'
}).attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
.submit();
};
// handler when files are dropped to a designated area.
// compose a multipart form data and submit it to the server
this.file_drop = function(e)
{
var files = e.target.files || e.dataTransfer.files;
if (!files || !files.length || !this.file_upload_size_check(files))
return;
// prepare multipart form data composition
var ts = new Date().getTime(),
formdata = window.FormData ? new FormData() : null,
fieldname = 'file[]',
boundary = '------multipartformboundary' + (new Date).getTime(),
dashdash = '--', crlf = '\r\n',
multipart = dashdash + boundary + crlf;
// inline function to submit the files to the server
var submit_data = function() {
var multiple = files.length > 1;
rcmail.display_progress({name: ts});
if (rcmail.env.files_progress_name)
file_api.file_upload_progress(ts, true);
// complete multipart content and post request
multipart += dashdash + boundary + dashdash + crlf;
$.ajax({
type: 'POST',
dataType: 'json',
url: file_api.env.url + file_api.url('file_upload', {folder: file_api.env.folder}),
contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
processData: false,
timeout: 0, // disable default timeout set in ajaxSetup()
data: formdata || multipart,
headers: {'X-Session-Token': file_api.env.token},
success: function(data) {
file_api.file_upload_progress_stop(ts);
file_api.file_upload_response(data);
},
error: function(o, status, err) {
file_api.file_upload_progress_stop(ts);
rcmail.http_error(o, status, err);
},
xhr: function() {
var xhr = jQuery.ajaxSettings.xhr();
if (!formdata && xhr.sendAsBinary)
xhr.send = xhr.sendAsBinary;
return xhr;
}
});
};
// upload progress supported (and handler exists)
// add progress ID to the request - need to be added before files
if (rcmail.env.files_progress_name) {
if (formdata)
formdata.append(rcmail.env.files_progress_name, ts);
else
multipart += 'Content-Disposition: form-data; name="' + rcmail.env.files_progress_name + '"'
+ crlf + crlf + ts + crlf + dashdash + boundary + crlf;
}
// get contents of all dropped files
var f, j, i = 0, last = files.length - 1;
for (j = 0; j <= last && (f = files[i]); i++) {
if (!f.name) f.name = f.fileName;
if (!f.size) f.size = f.fileSize;
if (!f.type) f.type = 'application/octet-stream';
// file name contains non-ASCII characters, do UTF8-binary string conversion.
if (!formdata && /[^\x20-\x7E]/.test(f.name))
f.name_bin = unescape(encodeURIComponent(f.name));
// do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
if (formdata) {
formdata.append(fieldname, f);
if (j == last)
return submit_data();
}
// use FileReader supporetd by Firefox 3.6
else if (window.FileReader) {
var reader = new FileReader();
// closure to pass file properties to async callback function
reader.onload = (function(file, j) {
return function(e) {
multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
multipart += 'Content-Length: ' + file.size + crlf;
multipart += 'Content-Type: ' + file.type + crlf + crlf;
multipart += reader.result + crlf;
multipart += dashdash + boundary + crlf;
if (j == last) // we're done, submit the data
return submit_data();
}
})(f,j);
reader.readAsBinaryString(f);
}
j++;
}
};
// upload progress requests
this.file_upload_progress = function(id, init)
{
if (init && id)
this.uploads[id] = this.env.folder;
setTimeout(function() {
if (id && file_api.uploads[id])
file_api.request('upload_progress', {id: id}, 'file_upload_progress_response');
}, rcmail.env.files_progress_time * 1000);
};
// upload progress response
this.file_upload_progress_response = function(response)
{
if (!this.response(response))
return;
var param = response.result;
if (!param.id || !this.uploads[param.id])
return;
if (param.total) {
param.name = param.id;
if (!param.done)
param.text = kolab_files_progress_str(param);
rcmail.display_progress(param);
}
if (!param.done && param.total)
this.file_upload_progress(param.id);
else
delete this.uploads[param.id];
};
this.file_upload_progress_stop = function(id)
{
if (id) {
delete this.uploads[id];
rcmail.display_progress({name: id});
}
};
// open file in new window, using file API viewer
this.file_open = function(file, viewer, params)
{
var args = {
_task: 'files',
_action: params && params.action ? params.action : 'open',
_file: file,
_viewer: viewer || 0
};
if (params && params.session)
args._session = params.session;
if (rcmail.env.extwin)
args._extwin = 1;
href = '?' + $.param(args);
if (params && params.local)
location.href = href;
else
rcmail.open_window(href, false, true);
};
// save file
this.file_save = function(file, content)
{
rcmail.enable_command('files-save', false);
// because we currently can edit only text files
// and we do not expect them to be very big, we save
// file in a very simple way, no upload progress, etc.
this.req = this.set_busy(true, 'saving');
this.request('file_update', {file: file, content: content, info: 1}, 'file_save_response');
};
// file save response handler
this.file_save_response = function(response)
{
rcmail.enable_command('files-save', true);
if (!this.response(response))
return;
// update file properties table
var table = $('#fileinfobox table'), file = response.result;
if (file) {
$('td.filetype', table).text(file.type);
$('td.filesize', table).text(this.file_size(file.size));
$('td.filemtime', table).text(file.mtime);
}
};
// document session delete request
this.document_delete = function(id)
{
this.req = this.set_busy(true, 'kolab_files.sessionterminating');
this.deleted_session = id;
this.request('document_delete', {id: id}, 'document_delete_response');
};
// document session delete response handler
this.document_delete_response = function(response)
{
if (!this.response(response))
return;
if (rcmail.task == 'files' && rcmail.env.action == 'edit')
window.close();
// @todo: force sessions info update
var win = window, list = rcmail.sessionslist;
if (!list) {
win = window.opener;
if (win && win.rcmail && win.file_api)
list = win.rcmail.sessionslist;
}
// remove session from the list (if sessions list exist)
if (list) {
list.remove(this.deleted_session);
delete win.file_api.env.sessions_list[this.deleted_session];
}
};
// Invite document session participants
this.document_invite = function(id, attendees, comment)
{
var list = [];
// expect attendees to be email => name hash
$.each(attendees || {}, function() { list.push(this); });
if (list.length) {
this.req = this.set_busy(true, 'kolab_files.documentinviting');
this.request('document_invite', {id: id, users: list, comment: comment || ''}, 'document_invite_response');
}
};
// document invite response handler
this.document_invite_response = function(response)
{
if (!this.response(response) || !response.result)
return;
var info = rcmail.env.file_data,
table = $('#document-editors-dialog table > tbody');
$.each(response.result.list || {}, function() {
var record = kolab_files_attendee_record(this.user, this.status, this.user_name);
table.append(record);
if (info.session && info.session.invitations)
info.session.invitations.push($.extend({status: 'invited', record: record}, this));
});
};
// Cancel invitations to an editing session
this.document_cancel = function(id, attendees)
{
if (attendees.length) {
this.req = this.set_busy(true, 'kolab_files.documentcancelling');
this.request('document_cancel', {id: id, users: attendees}, 'document_cancel_response');
}
};
// document_cancel response handler
this.document_cancel_response = function(response)
{
if (!this.response(response) || !response.result)
return;
var info = rcmail.env.file_data;
$.each(response.result.list || {}, function(i, user) {
var invitations = [];
$.each(info.session.invitations, function(i, u) {
if (u.user == user && u.record)
u.record.remove();
else
invitations.push(u);
});
info.session.invitations = invitations;
});
};
// handle auth errors on folder list
this.folder_list_auth_errors = function(result)
{
if (result && result.auth_errors) {
if (!this.auth_errors)
this.auth_errors = {};
$.extend(this.auth_errors, result.auth_errors);
}
// ask for password to the first storage on the list
$.each(this.auth_errors || [], function(i, v) {
file_api.folder_list_auth_dialog(i, v);
return false;
});
};
// create dialog for user credentials of external storage
this.folder_list_auth_dialog = function(label, driver)
{
var args = {width: 400, height: 300, buttons: {}},
dialog = $('#files-folder-auth-dialog'),
content = this.folder_list_auth_form(driver);
dialog.find('table.propform').remove();
$('.auth-options', dialog).before(content);
args.buttons[this.t('kolab_files.save')] = function() {
var data = {folder: label, list: 1};
$('input', dialog).each(function() {
data[this.name] = this.type == 'checkbox' && !this.checked ? '' : this.value;
});
file_api.open_dialog = this;
file_api.req = file_api.set_busy(true, 'kolab_files.authenticating');
file_api.request('folder_auth', data, 'folder_auth_response');
};
args.buttons[this.t('kolab_files.cancel')] = function() {
delete file_api.auth_errors[label];
kolab_dialog_close(this);
// go to the next one
file_api.folder_list_auth_errors();
};
args.title = this.t('kolab_files.folderauthtitle').replace('$title', label);
// show dialog window
kolab_dialog_show(dialog, args, function() {
// focus first empty input
$('input', dialog).each(function() {
if (!this.value) {
this.focus();
return false;
}
});
});
};
// folder_auth handler
this.folder_auth_response = function(response)
{
if (!this.response(response))
return;
var folders, found,
folder = response.result.folder,
id = 'rcmli' + rcmail.html_identifier_encode(folder),
parent = $('#' + id);
// try parent window if the folder element does not exist
if (!parent.length && window.parent && window.parent.rcmail) {
parent = $('#' + id, window.parent.document.body);
}
delete this.auth_errors[folder];
kolab_dialog_close(this.open_dialog);
// go to the next one
this.folder_list_auth_errors();
// parse result
folders = this.folder_list_parse(response.result.list);
delete folders[folder]; // remove root added in folder_list_parse()
// add folders from the external source to the list
$.each(folders, function(i, f) {
file_api.folder_list_row(i, f, parent.get(0));
found = true;
});
// reset folders list widget
if (found)
rcmail.folder_list.reset(true);
// add tree icons
// this.folder_list_tree(folders);
$.extend(this.env.folders, folders);
};
// returns content of the external storage authentication form
this.folder_list_auth_form = function(driver)
{
var rows = [];
$.each(driver.form, function(fi, fv) {
var id = 'authinput' + fi,
attrs = {type: fi.match(/pass/) ? 'password' : 'text', size: 25, name: fi, id: id},
input = $('<input>').attr(attrs);
if (driver.form_values && driver.form_values[fi])
input.attr({value: driver.form_values[fi]});
rows.push($('<tr>')
.append($('<td class="title">').append($('<label>').attr('for', id).text(fv)))
.append($('<td>').append(input))
);
});
return $('<table class="propform">').append(rows);
};
};
diff --git a/plugins/kolab_notes/notes.js b/plugins/kolab_notes/notes.js
index be9097d7..cd52c3bf 100644
--- a/plugins/kolab_notes/notes.js
+++ b/plugins/kolab_notes/notes.js
@@ -1,1744 +1,1743 @@
/**
* Client scripts for the Kolab Notes plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program 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.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
function rcube_kolab_notes_ui(settings)
{
/* private vars */
var ui_loading = false;
var saving_lock;
var search_query;
var folder_drop_target;
var notebookslist;
var noteslist;
var notesdata = {};
var tagsfilter = [];
var tags = [];
var search_request;
var search_query;
var tag_draghelper;
var render_no_focus;
var me = this;
/* public members */
this.selected_list = null;
this.selected_note = null;
this.notebooks = rcmail.env.kolab_notebooks || {};
/**
* initialize the notes UI
*/
function init()
{
// register button commands
rcmail.register_command('createnote', function(){
warn_unsaved_changes(function(){ edit_note(null, 'new'); })
}, false);
rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, true);
rcmail.register_command('list-edit', function(){ list_edit_dialog(me.selected_list); }, false);
rcmail.register_command('list-delete', function(){ list_delete(me.selected_list); }, false);
rcmail.register_command('list-remove', function(){ list_remove(me.selected_list); }, false);
rcmail.register_command('list-sort', list_set_sort, true);
rcmail.register_command('save', save_note, true);
rcmail.register_command('delete', delete_notes, false);
rcmail.register_command('search', quicksearch, true);
rcmail.register_command('reset-search', reset_search, true);
rcmail.register_command('sendnote', send_note, false);
rcmail.register_command('print', print_note, false);
rcmail.register_command('history', show_history_dialog, false);
// register server callbacks
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', update_note);
rcmail.addEventListener('plugin.update_list', list_update);
rcmail.addEventListener('plugin.destroy_list', list_destroy);
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
rcmail.addEventListener('plugin.close_history_dialog', close_history_dialog);
rcmail.addEventListener('plugin.note_render_changelog', render_changelog);
rcmail.addEventListener('plugin.note_show_revision', render_revision);
rcmail.addEventListener('plugin.note_show_diff', show_diff);
// initialize folder selectors
if (settings.selected_list && !me.notebooks[settings.selected_list]) {
settings.selected_list = null;
}
for (var id in me.notebooks) {
if (me.notebooks[id].editable && !settings.selected_list) {
settings.selected_list = id;
}
}
var widget_class = window.kolab_folderlist || rcube_treelist_widget;
notebookslist = new widget_class(rcmail.gui_objects.notebooks, {
id_prefix: 'rcmliknb',
save_state: true,
selectable: true,
keyboard: false,
searchbox: '#notebooksearch',
search_action: 'notes/list',
search_sources: [ 'folders', 'users' ],
search_title: rcmail.gettext('listsearchresults','kolab_notes'),
check_droptarget: function(node) {
var list = me.notebooks[node.id];
return !node.virtual && list.editable && node.id != me.selected_list;
}
});
notebookslist.addEventListener('select', function(node) {
var id = node.id;
if (me.notebooks[id] && id != me.selected_list) {
warn_unsaved_changes(function(){
rcmail.enable_command('createnote', has_permission(me.notebooks[id], 'i'));
rcmail.enable_command('list-edit', has_permission(me.notebooks[id], 'a'));
rcmail.enable_command('list-delete', has_permission(me.notebooks[id], 'xa'));
rcmail.enable_command('list-remove', !me.notebooks[id]['default']);
fetch_notes(id); // sets me.selected_list
},
function(){
// restore previous selection
notebookslist.select(me.selected_list);
});
}
// unfocus clicked list item
$(notebookslist.get_item(id)).find('a.listname').first().blur();
});
notebookslist.addEventListener('subscribe', function(p) {
var list;
if ((list = me.notebooks[p.id])) {
list.subscribed = p.subscribed || false;
rcmail.http_post('list', { _do:'subscribe', _list:{ id:p.id, permanent:list.subscribed?1:0 } });
}
});
notebookslist.addEventListener('remove', function(p) {
if (me.notebooks[p.id] && !me.notebooks[p.id]['default']) {
list_remove(p.id);
}
});
notebookslist.addEventListener('insert-item', function(p) {
var list = p.data;
if (list && list.id && !list.virtual) {
me.notebooks[list.id] = list;
if (list.subscribed)
rcmail.http_post('list', { _do:'subscribe', _list:{ id:p.id, permanent:1 } });
}
});
notebookslist.addEventListener('click-item', function(p) {
// avoid link execution
return false;
});
notebookslist.addEventListener('search-complete', function(data) {
if (data.length)
rcmail.display_message(rcmail.gettext('nrnotebooksfound','kolab_notes').replace('$nr', data.length), 'voice');
else
rcmail.display_message(rcmail.gettext('nonotebooksfound','kolab_notes'), 'info');
});
$(rcmail.gui_objects.notebooks).on('click', 'div.folder > a.listname', function(e) {
var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
notebookslist.select(id);
e.preventDefault();
return false;
});
// register dbl-click handler to open list edit dialog
$(rcmail.gui_objects.notebooks).on('dblclick', 'li:not(.virtual) a', function(e) {
var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
if (me.notebooks[id] && has_permission(me.notebooks[id], 'a')) {
list_edit_dialog(id);
}
// clear text selection (from dbl-clicking)
var sel = window.getSelection ? window.getSelection() : document.selection;
if (sel && sel.removeAllRanges) {
sel.removeAllRanges();
}
else if (sel && sel.empty) {
sel.empty();
}
e.preventDefault();
return false;
});
// initialize notes list widget
if (rcmail.gui_objects.noteslist) {
noteslist = new rcube_list_widget(rcmail.gui_objects.noteslist,
{ multiselect:true, draggable:true, keyboard:true });
noteslist.addEventListener('select', function(list) {
render_no_focus = rcube_event._last_keyboard_event && $(list.list).has(rcube_event._last_keyboard_event.target);
var selection_changed = list.selection.length != 1 || !me.selected_note || list.selection[0] != me.selected_note.id;
selection_changed && warn_unsaved_changes(function(){
var note;
if (noteslist.selection.length == 1 && (note = notesdata[noteslist.selection[0]])) {
edit_note(note.uid, 'edit');
}
else {
reset_view();
}
},
function(){
// TODO: previous restore selection
list.select(me.selected_note.id);
});
rcmail.enable_command('delete', me.notebooks[me.selected_list] && has_permission(me.notebooks[me.selected_list], 'td') && list.selection.length > 0);
rcmail.enable_command('sendnote', list.selection.length > 0);
rcmail.enable_command('print', 'history', list.selection.length == 1);
})
.addEventListener('dragstart', function(e) {
folder_drop_target = null;
notebookslist.drag_start();
})
.addEventListener('dragmove', function(e) {
folder_drop_target = notebookslist.intersects(rcube_event.get_mouse_pos(e), true);
})
.addEventListener('dragend', function(e) {
notebookslist.drag_end();
// move dragged notes to this folder
if (folder_drop_target) {
noteslist.draglayer.hide();
// check unsaved changes first
var new_folder_id = folder_drop_target;
warn_unsaved_changes(
// ok
function() {
move_notes(new_folder_id);
reset_view();
noteslist.clear_selection();
},
// nok
undefined,
// beforesave
function(savedata) {
savedata.list = new_folder_id;
// remove from list and thus avoid being moved (again)
var id = me.selected_note.id;
noteslist.remove_row(id);
delete notesdata[id];
}
);
}
folder_drop_target = null;
})
.init().focus();
}
if (settings.sort_col) {
$('#notessortmenu a.by-' + settings.sort_col).addClass('selected');
}
// click-handler on tags list
$(rcmail.gui_objects.notestagslist).on('click', 'li', function(e){
var item = e.target.nodeName == 'LI' ? $(e.target) : $(e.target).closest('li'),
tag = item.data('value');
if (!tag)
return false;
// reset selection on regular clicks
var index = $.inArray(tag, tagsfilter);
var shift = e.shiftKey || e.ctrlKey || e.metaKey;
if (!shift) {
if (tagsfilter.length > 1)
index = -1;
$('li', rcmail.gui_objects.notestagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
item.removeClass('selected').attr('aria-checked', 'false');
var a = tagsfilter.slice(0,index);
tagsfilter = a.concat(tagsfilter.slice(index+1));
}
filter_notes();
// clear text selection in IE after shift+click
if (shift && document.selection)
document.selection.empty();
e.preventDefault();
return false;
})
.on('keypress', 'li', function(e) {
if (e.keyCode == 13) {
$(this).trigger('click', { pointerType:'keyboard' });
}
})
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
return false;
});
init_editor();
if (settings.selected_list) {
notebookslist.select(settings.selected_list)
}
}
this.init = init;
/**
*
*/
function init_dialog()
{
rcmail.register_command('save', save_note, true);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', function(data){
data.id = rcmail.html_identifier_encode(data.uid);
notesdata[data.id] = data;
render_note(data);
});
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
var id;
for (id in me.notebooks) {
if (me.notebooks[id].editable) {
me.selected_list = id;
break;
}
}
init_editor();
if (settings.selected_uid) {
me.selected_list = settings.selected_list;
edit_note(settings.selected_uid);
}
else {
setTimeout(function(){
me.selected_note = $.extend({
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
tags: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}, rcmail.env.kolab_notes_template || {});
render_note(me.selected_note);
}, 100);
}
}
this.init_dialog = init_dialog;
/**
* initialize tinyMCE editor
*/
function init_editor()
{
var editor_conf = {
selector: '#notecontent',
theme: 'modern',
language: settings.editor.lang,
content_css: rcmail.assets_path('program/js/tinymce/roundcube/content.css?v2'),
plugins: 'autolink charmap code link paste tabfocus searchreplace table textcolor',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify '
+ '| bullist numlist outdent indent blockquote | forecolor backcolor fontselect fontsizeselect '
+ '| link unlink table charmap | code searchreplace undo redo',
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
relative_urls: false,
remove_script_host: false,
convert_urls: false,
image_description: false,
paste_webkit_style: "color font-size font-family",
paste_data_images: true,
//spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1',
//spellchecker_language: rcmail.env.spell_lang,
accessibility_focus: false,
tabfocus_elements: [':prev','btn-save-note'],
setup: function(ed) {
// make links open on shift-click
ed.on('click', function(e) {
var link = $(e.target).closest('a');
if (link.length && e.shiftKey) {
if (!bw.mz) window.open(link.get(0).href, '_blank');
return false;
}
});
}
};
// support external configuration settings e.g. from skin
if (window.rcmail_editor_settings)
$.extend(editor_conf, window.rcmail_editor_settings);
tinyMCE.init(editor_conf);
// register click handler for message links
$(rcmail.gui_objects.notesattachmentslist).on('click', 'li a.messagelink', function(){
rcmail.open_window(this.href);
return false;
});
}
/**
* Quote HTML entities
*/
function Q(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
/**
* Trim whitespace off the given string
*/
function trim(str)
{
return String(str).replace(/\s+$/, '').replace(/^\s+/, '');
}
/**
* Check permissions on the given list object
*/
function has_permission(list, perm)
{
// multiple chars means "either of"
if (String(perm).length > 1) {
for (var i=0; i < perm.length; i++) {
if (has_permission(list, perm[i])) {
return true;
}
}
}
if (list.rights && String(list.rights).indexOf(perm) >= 0) {
return true;
}
return (perm == 'i' && list.editable);
}
/**
*
*/
function edit_note(uid, action)
{
if (!uid) {
me.selected_note = null;
if (noteslist)
noteslist.clear_selection();
me.selected_note = {
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
tags: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}
render_note(me.selected_note);
rcmail.enable_command('print', true);
}
else {
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('get', { _list:me.selected_list, _id:uid }, true);
}
}
/**
*
*/
function list_edit_dialog(id)
{
if (!rcmail.gui_containers.notebookeditform) {
return false;
}
// close show dialog first
var $dialog = rcmail.gui_containers.notebookeditform;
if ($dialog.is(':ui-dialog')) {
$dialog.dialog('close');
}
var list = me.notebooks[id] || { name:'', editable:true, rights: 'riwta' };
var form, name;
$dialog.html(rcmail.get_label('loading'));
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('list'),
data: { _do: (list.id ? 'form-edit' : 'form-new'), _list: { id: list.id } },
success: function(data) {
$dialog.html(data);
rcmail.triggerEvent('kolab_notes_editform_load', list);
// resize and reposition dialog window
form = $('#noteslistpropform');
var win = $(window), w = win.width(), h = win.height();
- $dialog.dialog('option', { height: Math.min(h-20, form.height()+130), width: Math.min(w-20, form.width()+50) })
- .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
+ $dialog.dialog('option', { height: Math.min(h-20, form.height()+130), width: Math.min(w-20, form.width()+50) });
name = $('#noteslist-name').prop('disabled', !has_permission(list, 'a')).val(list.editname || list.name);
name.select();
}
});
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save')] = function() {
// form is not loaded
if (!form || !form.length)
return;
// do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidlistproperties', 'kolab_notes'));
name.select();
return;
}
// post data to server
var data = form.serializeJSON();
if (list.id)
data.id = list.id;
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: (list.id ? 'edit' : 'new'), _list: data });
$dialog.dialog('close');
};
buttons[rcmail.gettext('cancel')] = function() {
$dialog.dialog('close');
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
title: rcmail.gettext((list.id ? 'editlist' : 'newnotebook'), 'kolab_notes'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$dialog.html('').dialog('destroy').hide();
},
buttons: buttons,
minWidth: 480,
width: 640
}).show();
}
/**
* Callback from server after changing list properties
*/
function list_update(prop)
{
if (prop._reload) {
rcmail.redirect(rcmail.url('', { _list: (prop.newid || prop.id) }));
}
else if (prop.newid && prop.newid != prop.id) {
var book = $.extend({}, me.notebooks[prop.id]);
book.id = prop.newid;
book.name = prop.name;
book.listname = prop.listname;
book.editname = prop.editname || prop.name;
me.notebooks[prop.newid] = book;
delete me.notebooks[prop.id];
// update treelist item
var li = $(notebookslist.get_item(prop.id));
$('.listname', li).html(prop.listname);
notebookslist.update(prop.id, { id:book.id, html:li.html() });
// link all loaded note records to the new list id
if (me.selected_list == prop.id) {
me.selected_list = prop.newid;
for (var k in notesdata) {
if (notesdata[k].list == prop.id) {
notesdata[k].list = book.id;
}
}
notebookslist.select(prop.newid);
}
}
}
/**
*
*/
function list_delete(id)
{
var list = me.notebooks[id];
if (list && confirm(rcmail.gettext('deletenotebookconfirm', 'kolab_notes'))) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: 'delete', _list: { id: list.id } });
}
}
/**
*
*/
function list_remove(id)
{
if (me.notebooks[id]) {
list_destroy(me.notebooks[id]);
rcmail.http_post('list', { _do:'subscribe', _list:{ id:id, permanent:0, recursive:1 } });
}
}
/**
* Callback from server on list delete command
*/
function list_destroy(prop)
{
if (!me.notebooks[prop.id]) {
return;
}
notebookslist.remove(prop.id);
delete me.notebooks[prop.id];
if (me.selected_list == prop.id) {
for (id in me.notebooks) {
if (me.notebooks[id]) {
notebookslist.select(id);
break;
}
}
}
}
/**
* Change notes list sort order
*/
function list_set_sort(col)
{
if (settings.sort_col != col) {
settings.sort_col = col;
$('#notessortmenu a').removeClass('selected').filter('.by-' + col).addClass('selected');
rcmail.save_pref({ name: 'kolab_notes_sort_col', value: col });
// re-sort table in DOM
$(noteslist.tbody).children().sortElements(function(la, lb){
var a_id = String(la.id).replace(/^rcmrow/, ''),
b_id = String(lb.id).replace(/^rcmrow/, ''),
a = notesdata[a_id],
b = notesdata[b_id];
if (!a || !b) {
return 0;
}
else if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
}
}
/**
* Execute search
*/
function quicksearch()
{
var q;
if (rcmail.gui_objects.qsearchbox && (q = rcmail.gui_objects.qsearchbox.value)) {
var id = 'search-'+q;
// ignore if query didn't change
if (search_request == id)
return;
warn_unsaved_changes(function(){
search_request = id;
search_query = q;
fetch_notes();
},
function(){
reset_search();
});
}
else { // empty search input equals reset
reset_search();
}
}
/**
* Reset search and get back to normal listing
*/
function reset_search()
{
$(rcmail.gui_objects.qsearchbox).val('');
if (search_request) {
search_request = search_query = null;
fetch_notes();
}
}
/**
*
*/
function fetch_notes(id)
{
if (rcmail.busy)
return;
if (id && id != me.selected_list) {
me.selected_list = id;
}
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('fetch', { _list:me.selected_list, _q:search_query }, true);
reset_view();
noteslist.clear(true);
notesdata = {};
tagsfilter = [];
update_state();
}
function filter_notes()
{
// tagsfilter
var note, tr, match;
for (var id in noteslist.rows) {
tr = noteslist.rows[id].obj;
note = notesdata[id];
match = note.tags && note.tags.length;
for (var i=0; match && note && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], note.tags) < 0)
match = false;
}
if (match || !tagsfilter.length) {
$(tr).show();
}
else {
$(tr).hide();
}
if (me.selected_note && me.selected_note.uid == note.uid && !match) {
warn_unsaved_changes(function(){
me.selected_note = null;
noteslist.clear_selection();
}, function(){
tagsfilter = [];
filter_notes();
update_tagcloud();
});
}
}
}
/**
*
*/
function data_ready(data)
{
data.data.sort(function(a,b){
if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
var i, id, rec;
for (i=0; data.data && i < data.data.length; i++) {
rec = data.data[i];
rec.id = rcmail.html_identifier_encode(rec.uid);
noteslist.insert_row({
id: 'rcmrow' + rec.id,
cols: [
{ className:'title', innerHTML:Q(rec.title) },
{ className:'date', innerHTML:Q(rec.changed || '') }
]
});
notesdata[rec.id] = rec;
}
render_tagslist(data.tags || [], !data.search)
rcmail.set_busy(false, 'loading', ui_loading);
// select the single result
if (data.data.length == 1) {
noteslist.select(data.data[0].id);
}
else if (settings.selected_uid) {
noteslist.select(rcmail.html_identifier_encode(settings.selected_uid));
delete settings.selected_uid;
}
else if (me.selected_note && notesdata[me.selected_note.id]) {
noteslist.select(me.selected_note.id);
}
else if (!data.data.length) {
rcmail.display_message(rcmail.gettext('norecordsfound','kolab_notes'), 'info');
}
}
/**
*
*/
function render_note(data, container, temp, retry)
{
rcmail.set_busy(false, 'loading', ui_loading);
if (!data) {
rcmail.display_message(rcmail.get_label('recordnotfound', 'kolab_notes'), 'error');
return;
}
if (!container) {
container = rcmail.gui_containers['notedetailview'];
}
var list = me.notebooks[data.list] || me.notebooks[me.selected_list] || { rights: 'lrs', editable: false };
content = $('#notecontent').val(data.description),
readonly = data.readonly || !(list.editable || !data.uid && has_permission(list,'i')),
attachmentslist = gui_object('notesattachmentslist', container).html(''),
titlecontainer = container || rcmail.gui_objects.noteviewtitle;
$('.notetitle', titlecontainer).val(data.title).prop('disabled', readonly).show();
$('.dates .notecreated', titlecontainer).html(Q(data.created || ''));
$('.dates .notechanged', titlecontainer).html(Q(data.changed || ''));
if (data.created || data.changed) {
$('.dates', titlecontainer).show();
}
// tag-edit line
var tagline = $('.tagline', titlecontainer).empty()[readonly?'addClass':'removeClass']('disabled').show();
$.each(typeof data.tags == 'object' && data.tags.length ? data.tags : [''], function(i,val) {
$('<input>')
.attr('name', 'tags[]')
.attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
if (!data.tags || !data.tags.length) {
$('<span>').addClass('placeholder')
.html(rcmail.gettext('notags', 'kolab_notes'))
.appendTo(tagline)
.click(function(e) { $(this).parent().find('.tagedit-list').trigger('click'); });
}
$('.tagline input.tag', titlecontainer).tagedit({
animSpeed: 100,
allowEdit: false,
allowAdd: !readonly,
allowDelete: !readonly,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: tags, minLength: 0, noCheck: true },
texts: { removeLinkTitle: rcmail.gettext('removetag', 'kolab_notes') }
});
if (data.links) {
$.each(data.links, function(i, link) {
var li = $('<li>').addClass('link')
.addClass('message eml')
.append($('<a>')
.attr('href', link.mailurl)
.addClass('messagelink')
.text(link.subject || link.uri)
)
.appendTo(attachmentslist);
if (!readonly && !data._from_mail) {
$('<a>')
.attr('href', '#delete')
.attr('title', rcmail.gettext('removelink', 'kolab_notes'))
.addClass('delete')
.html(rcmail.gettext('delete'))
.click({ uri:link.uri }, function(e) {
remove_link(this, e.data.uri);
return false;
})
.appendTo(li);
}
});
}
if (!readonly) {
$('.tagedit-list', titlecontainer)
.on('click', function(){ $('.tagline .placeholder').hide(); });
}
if (!data.list)
data.list = list.id;
data.readonly = readonly;
if (!temp) {
$(rcmail.gui_objects.notebooks).filter('select').val(list.id);
me.selected_note = data;
me.selected_note.id = rcmail.html_identifier_encode(data.uid);
rcmail.enable_command('save', !readonly);
}
var html = data.html || data.description;
// convert plain text to HTML and make URLs clickable
if (html != '' && (!data.html || !html.match(/<(html|body)/))) {
html = text2html(html);
}
var node, editor = tinyMCE.get('notecontent'), is_html = false;
retry = retry || 0;
// sometimes the editor instance is not ready yet (FF only)...
if (!readonly && !editor && $('#notecontent').length && retry < 5) {
// ... give it some more time
setTimeout(function() {
gui_object('noteseditform', container).show();
render_note(data, container, temp, retry+1);
}, 200);
return;
}
if (!readonly && editor) {
gui_object('notesdetailview', container).hide();
gui_object('noteseditform', container).show();
editor.setContent(''); // #4781
editor.setContent(html);
node = editor.getContentAreaContainer().childNodes[0];
if (node) node.tabIndex = content.get(0).tabIndex;
if (me.selected_note.uid) {
if (!render_no_focus)
editor.getBody().focus();
}
else
$('.notetitle', titlecontainer).focus().select();
// read possibly re-formatted content back from editor for later comparison
me.selected_note.description = editor.getContent({ format:'html' }).replace(/^\s*(<p><\/p>\n*)?/, '');
is_html = true;
}
else {
gui_object('noteseditform', container).hide();
gui_object('notesdetailview', container).html(html).show();
}
render_no_focus = false;
// notify subscribers
rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
if (rcmail.is_framed())
parent.rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
// Trigger resize (needed for proper editor resizing)
$(window).resize();
}
/**
*
*/
function gui_object(name, container)
{
var elem = rcmail.gui_objects[name], selector = elem;
if (elem && elem.className && container) {
selector = '.' + String(elem.className).split(' ').join('.');
}
else if (elem && elem.id) {
selector = '#' + elem.id;
}
return $(selector, container);
}
/**
* Convert the given plain text to HTML contents to be displayed in editor
*/
function text2html(str)
{
// simple link parser (similar to rcube_string_replacer class in PHP)
var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})',
url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-',
link_pattern = new RegExp('([hf]t+ps?://|www.)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig'),
link_replace = function(matches, p1, p2) {
var url = (p1 == 'www.' ? 'http://' : '') + p1 + p2;
return '<a href="' + url + '" class="x-templink">' + p1 + p2 + '</a>';
};
return '<pre>' + Q(str).replace(link_pattern, link_replace) + '</pre>';
}
/**
*
*/
function show_history_dialog()
{
var dialog, rec = me.selected_note;
if (!rec || !rec.uid || !window.libkolab_audittrail) {
return false;
}
// render dialog
$dialog = libkolab_audittrail.object_history_dialog({
module: 'kolab_notes',
container: '#notehistory',
title: rcmail.gettext('objectchangelog','kolab_notes') + ' - ' + rec.title,
// callback function for list actions
listfunc: function(action, rev) {
var rec = $dialog.data('rec');
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('action', { _do: action, _data: { uid: rec.uid, list:rec.list, rev: rev } }, saving_lock);
},
// callback function for comparing two object revisions
comparefunc: function(rev1, rev2) {
var rec = $dialog.data('rec');
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('action', { _do: 'diff', _data: { uid: rec.uid, list: rec.list, rev1: rev1, rev2: rev2 } }, saving_lock);
}
});
$dialog.data('rec', rec);
// fetch changelog data
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('action', { _do: 'changelog', _data: { uid: rec.uid, list: rec.list } }, saving_lock);
}
/**
*
*/
function render_changelog(data)
{
var $dialog = $('#notehistory'),
rec = $dialog.data('rec');
if (data === false || !data.length || !rec) {
// display 'unavailable' message
$('<div class="notfound-message note-dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','kolab_notes') + '</div>')
.insertBefore($dialog.find('.changelog-table').hide());
return;
}
data.module = 'kolab_notes';
libkolab_audittrail.render_changelog(data, rec, me.notebooks[rec.list]);
}
/**
*
*/
function render_revision(data)
{
data.readonly = true;
// clone view and render data into a dialog
var model = rcmail.gui_containers['notedetailview'],
container = model.clone();
container
.removeAttr('id style class role')
.find('.mce-container').remove();
// reset custom styles
container.children('div, form').removeAttr('id style');
// open jquery UI dialog
container.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: data.title + ' @ ' + data.rev,
close: function() {
container.dialog('destroy').remove();
},
buttons: [
{
text: rcmail.gettext('close'),
click: function() { container.dialog('close'); },
autofocus: true
}
],
width: model.width(),
height: model.height(),
minWidth: 450,
minHeight: 400
})
.show();
render_note(data, container, true);
}
/**
*
*/
function show_diff(data)
{
var rec = me.selected_note,
$dialog = $('#notediff');
$dialog.find('div.form-section, h2.note-title-new').hide().data('set', false);
// always show title
$('.note-title', $dialog).text(rec.title).removeClass('diff-text-old').show();
// show each property change
$.each(data.changes, function(i, change) {
var prop = change.property, r2, html = false,
row = $('div.note-' + prop, $dialog).first();
// special case: title
if (prop == 'title') {
$('.note-title', $dialog).addClass('diff-text-old').text(change['old'] || '--');
$('.note-title-new', $dialog).text(change['new'] || '--').show();
}
// no display container for this property
if (!row.length) {
return true;
}
if (change.diff_) {
row.children('.diff-text-diff').html(change.diff_);
row.children('.diff-text-old, .diff-text-new').hide();
}
else {
if (!html) {
// escape HTML characters
change.old_ = Q(change.old_ || change['old'] || '--')
change.new_ = Q(change.new_ || change['new'] || '--')
}
row.children('.diff-text-old').html(change.old_ || change['old'] || '--').show();
row.children('.diff-text-new').html(change.new_ || change['new'] || '--').show();
}
row.show().data('set', true);
});
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('objectdiff','kolab_notes').replace('$rev1', data.rev1).replace('$rev2', data.rev2) + ' - ' + rec.title,
open: function() {
$dialog.attr('aria-hidden', 'false');
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
},
buttons: [
{
text: rcmail.gettext('close'),
click: function() { $dialog.dialog('close'); },
autofocus: true
}
],
minWidth: 400,
width: 480
}).show();
// set dialog size according to content
libkolab_audittrail.dialog_resize($dialog.get(0), $dialog.height(), rcmail.gui_containers.notedetailview.width() - 40);
}
// close the event history dialog
function close_history_dialog()
{
$('#notehistory, #notediff').each(function(i, elem) {
var $dialog = $(elem);
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
});
};
/**
*
*/
function remove_link(elem, uri)
{
// remove the link item matching the given uri
me.selected_note.links = $.grep(me.selected_note.links, function(link) { return link.uri != uri; });
me.selected_note._links_removed = true
// remove UI list item
$(elem).hide().closest('li').addClass('deleted');
}
/**
* Open a new window to print the currently selected note
*/
function print_note()
{
var printwin, data, list;
if (me.selected_note && (printwin = rcmail.open_window(settings.print_template))) {
list = me.notebooks[me.selected_note.list] || me.notebooks[me.selected_list] || {};
data = get_save_data();
// for read-only notes, get_save_data() won't return the content
if (me.selected_note.readonly || !list.editable) {
data.description = me.selected_note.html || me.selected_note.description;
if (!me.selected_note.html || !data.description.match(/<(html|body)/)) {
data.description = text2html(data.description);
}
}
$(printwin).load(function(){
printwin.document.title = data.title;
$('#notetitle', printwin.document).html(Q(data.title));
$('#notebody', printwin.document).html(data.description);
$('#notetags', printwin.document).html('<span class="tag">' + data.tags.join('</span><span class="tag">') + '</span>');
$('#notecreated', printwin.document).html(Q(me.selected_note.created));
$('#notechanged', printwin.document).html(Q(me.selected_note.changed));
printwin.print();
});
}
}
/**
* Redirect to message compose screen with UIDs of notes to be appended
*/
function send_note()
{
var uids = [];
for (var rec, i=0; i < noteslist.selection.length; i++) {
if (rec = notesdata[noteslist.selection[i]]) {
uids.push(rec.uid);
// TODO: check if rec.uid == me.selected_note.uid and unsaved changes
}
}
if (uids.length) {
rcmail.goto_url('mail/compose', { _with_notes: uids.join(','), _notes_list: me.selected_list }, true);
}
}
/**
*
*/
function render_tagslist(newtags, replace)
{
if (replace) {
tags = newtags;
}
else {
var append = [];
for (var i=0; i < newtags.length; i++) {
if ($.inArray(newtags[i], tags) < 0)
append.push(newtags[i]);
}
if (!append.length) {
update_tagcloud();
return; // nothing to be added
}
tags = tags.concat(append);
}
// sort tags first
tags.sort(function(a,b){
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
})
var widget = $(rcmail.gui_objects.notestagslist).html('');
// append tags to tag cloud
$.each(tags, function(i, tag){
li = $('<li role="checkbox" aria-checked="false" tabindex="0"></li>')
.attr('rel', tag)
.data('value', tag)
.html(Q(tag) + '<span class="count"></span>')
.appendTo(widget)
.draggable({
addClasses: false,
revert: 'invalid',
revertDuration: 300,
helper: tag_draggable_helper,
start: tag_draggable_start,
appendTo: 'body',
cursor: 'pointer'
});
});
update_tagcloud();
}
/**
* Display the given counts to each tag and set those inactive which don't
* have any matching records in the current view.
*/
function update_tagcloud(counts)
{
// compute counts first by iterating over all visible task items
if (typeof counts == 'undefined') {
counts = {};
$.each(notesdata, function(id, rec){
for (var t, j=0; rec && rec.tags && j < rec.tags.length; j++) {
t = rec.tags[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
});
}
$(rcmail.gui_objects.notestagslist).children('li').each(function(i,li){
var elem = $(li), tag = elem.attr('rel'),
count = counts[tag] || 0;
elem.children('.count').html(count+'');
if (count == 0) elem.addClass('inactive');
else elem.removeClass('inactive');
if (tagsfilter && tagsfilter.length && $.inArray(tag, tagsfilter)) {
elem.addClass('selected').attr('aria-checked', 'true');
}
else {
elem.removeClass('selected').attr('aria-checked', 'false');
}
});
}
/**
* Callback from server after saving a note record
*/
function update_note(data)
{
data.id = rcmail.html_identifier_encode(data.uid);
var row, is_new = (notesdata[data.id] == undefined && data.list == me.selected_list);
notesdata[data.id] = data;
if (is_new || me.selected_note && data.id == me.selected_note.id) {
render_note(data);
render_tagslist(data.tags || []);
}
else if (data.tags) {
render_tagslist(data.tags);
}
// add list item on top
if (is_new) {
noteslist.insert_row({
id: 'rcmrow' + data.id,
cols: [
{ className:'title', innerHTML:Q(data.title) },
{ className:'date', innerHTML:Q(data.changed || '') }
]
}, true);
noteslist.select(data.id);
}
// update list item
else if (row = noteslist.rows[data.id]) {
$('.title', row.obj).html(Q(data.title));
$('.date', row.obj).html(Q(data.changed || ''));
// TODO: move to top
}
}
/**
*
*/
function reset_view()
{
close_history_dialog();
me.selected_note = null;
$('.notetitle', rcmail.gui_objects.noteviewtitle).val('').hide();
$('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide();
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.notesattachmentslist).html('');
rcmail.enable_command('save', false);
}
/**
* Collect data from the edit form and submit it to the server
*/
function save_note(beforesave)
{
if (!me.selected_note) {
return false;
}
var savedata = get_save_data();
// run savedata through the given callback function
if (typeof beforesave == 'function') {
beforesave(savedata);
}
// add reference to old list if changed
if (me.selected_note.list && savedata.list != me.selected_note.list) {
savedata._fromlist = me.selected_note.list;
}
// do some input validation
if (savedata.title == '') {
alert(rcmail.gettext('entertitle', 'kolab_notes'));
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus();
return false;
}
if (check_change_state(savedata)) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: savedata.uid?'edit':'new' }, true);
}
else {
rcmail.display_message(rcmail.get_label('nochanges', 'kolab_notes'), 'info');
}
}
/**
* Collect updated note properties from edit form for saving
*/
function get_save_data()
{
var editor = tinyMCE.get('notecontent'),
listselect = $('option:selected', rcmail.gui_objects.notebooks);
var savedata = {
title: trim($('.notetitle', rcmail.gui_objects.noteviewtitle).val()),
description: editor ? editor.getContent({ format:'html' }).replace(/^\s*(<p><\/p>\n*)?/, '') : $('#notecontent').val(),
list: listselect.length ? listselect.val() : me.selected_note.list || me.selected_list,
uid: me.selected_note.uid,
tags: []
};
// copy links
if ($.isArray(me.selected_note.links)) {
savedata.links = me.selected_note.links;
}
// collect tags
$('.tagedit-list input[type="hidden"]', rcmail.gui_objects.noteviewtitle).each(function(i, elem){
if (elem.value)
savedata.tags.push(elem.value);
});
// including the "pending" one in the text box
var newtag = $('#tagedit-input').val();
if (newtag != '') {
savedata.tags.push(newtag);
}
return savedata;
}
/**
* Check if the currently edited note record was changed
*/
function check_change_state(data)
{
if (!me.selected_note || me.selected_note.readonly || !has_permission(me.notebooks[me.selected_note.list || me.selected_list], 'i')) {
return false;
}
var savedata = data || get_save_data();
return savedata.title != me.selected_note.title
|| savedata.description != me.selected_note.description
|| savedata.tags.join(',') != (me.selected_note.tags || []).join(',')
|| savedata.list != me.selected_note.list
|| me.selected_note._links_removed;
}
/**
* Check for unsaved changes and warn the user
*/
function warn_unsaved_changes(ok, nok, beforesave)
{
if (typeof ok != 'function')
ok = function(){ };
if (typeof nok != 'function')
nok = function(){ };
if (check_change_state()) {
var dialog, buttons = [];
buttons.push({
text: rcmail.gettext('discard', 'kolab_notes'),
click: function() {
dialog.dialog('close');
ok();
}
});
buttons.push({
text: rcmail.gettext('save'),
'class': 'mainaction',
click: function() {
save_note(beforesave);
dialog.dialog('close');
rcmail.busy = false; // don't block next action
ok();
}
});
buttons.push({
text: rcmail.gettext('abort', 'kolab_notes'),
click: function() {
dialog.dialog('close');
nok();
}
});
var options = {
width: 460,
resizable: false,
closeOnEscape: false,
dialogClass: 'warning',
open: function(event, ui) {
$(this).parent().find('.ui-dialog-titlebar-close').hide();
setTimeout(function(){
dialog.parent().find('.ui-button:visible').first().focus();
}, 10);
},
close: function(event, ui) {
$(this).dialog('destroy').remove();
}
};
// open jquery UI dialog
dialog = rcmail.show_popup_dialog(
rcmail.gettext('discardunsavedchanges', 'kolab_notes'),
rcmail.gettext('unsavedchanges', 'kolab_notes'),
buttons,
options
);
return false;
}
if (typeof ok == 'function') {
ok();
}
return true;
}
/**
*
*/
function delete_notes()
{
if (!noteslist.selection.length) {
return false;
}
if (confirm(rcmail.gettext('deletenotesconfirm','kolab_notes'))) {
var rec, id, uids = [];
for (var i=0; i < noteslist.selection.length; i++) {
id = noteslist.selection[i];
rec = notesdata[id];
if (rec) {
noteslist.remove_row(id);
uids.push(rec.uid);
delete notesdata[id];
}
}
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list }, _do: 'delete' }, true);
reset_view();
update_tagcloud();
noteslist.clear_selection();
}
}
/**
*
*/
function move_notes(list_id)
{
var rec, id, uids = [];
for (var i=0; i < noteslist.selection.length; i++) {
id = noteslist.selection[i];
rec = notesdata[id];
if (rec) {
noteslist.remove_row(id);
uids.push(rec.uid);
delete notesdata[id];
}
}
if (uids.length) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list, to: list_id }, _do: 'move' }, true);
}
}
/**
* update browser location to remember current view
*/
function update_state()
{
var query = { _list: me.selected_list }
if (settings.selected_uid) {
query._id = settings.selected_uid;
}
if (window.history.replaceState) {
window.history.replaceState({}, document.title, rcmail.url('', query));
}
}
/* Helper functions for drag & drop functionality of tags */
function tag_draggable_helper()
{
if (!tag_draghelper)
tag_draghelper = $('<div class="tag-draghelper"></div>');
else
tag_draghelper.html('');
$(this).clone().addClass('tag').appendTo(tag_draghelper);
return tag_draghelper;
}
function tag_draggable_start(event, ui)
{
// register notes list to receive drop events
$('li', rcmail.gui_objects.noteslist).droppable({
hoverClass: 'droptarget',
accept: tag_droppable_accept,
drop: tag_draggable_dropped,
addClasses: false
});
// allow to drop tags onto edit form title
$(rcmail.gui_objects.noteviewtitle).droppable({
drop: function(event, ui){
$('#tagedit-input').val(ui.draggable.data('value')).trigger('transformToTag');
$('.tagline .placeholder', rcmail.gui_objects.noteviewtitle).hide();
},
addClasses: false
})
}
function tag_droppable_accept(draggable)
{
if (rcmail.busy)
return false;
var tag = draggable.data('value'),
drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
drop_rec = notesdata[drop_id];
// target already has this tag assigned
if (!drop_rec || (drop_rec.tags && $.inArray(tag, drop_rec.tags) >= 0)) {
return false;
}
return true;
}
function tag_draggable_dropped(event, ui)
{
var drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
tag = ui.draggable.data('value'),
rec = notesdata[drop_id],
savedata;
if (rec && rec.id) {
savedata = me.selected_note && rec.uid == me.selected_note.uid ? get_save_data() : $.extend({}, rec);
if (savedata.id) delete savedata.id;
if (savedata.html) delete savedata.html;
if (!savedata.tags)
savedata.tags = [];
savedata.tags.push(tag);
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: 'edit' }, true);
}
}
}
// extend jQuery
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
jQuery.fn.sortElements = (function(){
var sort = [].sort;
return function(comparator, getSortable) {
getSortable = getSortable || function(){ return this };
var last = null;
return sort.call(this, comparator).each(function(i){
// at this point the array is sorted, so we can just detach each one from wherever it is, and add it after the last
var node = $(getSortable.call(this));
var parent = node.parent();
if (last) last.after(node);
else parent.prepend(node);
last = node;
});
};
})();
/* notes plugin UI initialization */
var kolabnotes;
window.rcmail && rcmail.addEventListener('init', function(evt) {
kolabnotes = new rcube_kolab_notes_ui(rcmail.env.kolab_notes_settings);
if (rcmail.env.action == 'dialog-ui')
kolabnotes.init_dialog();
else
kolabnotes.init();
});
diff --git a/plugins/libkolab/js/audittrail.js b/plugins/libkolab/js/audittrail.js
index 84fdf61b..5ec6d091 100644
--- a/plugins/libkolab/js/audittrail.js
+++ b/plugins/libkolab/js/audittrail.js
@@ -1,269 +1,268 @@
/**
* Kolab groupware audit trail utilities
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program 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.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
var libkolab_audittrail = {}
libkolab_audittrail.quote_html = function(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
// show object changelog in a dialog
libkolab_audittrail.object_history_dialog = function(p)
{
// render dialog
var $dialog = $(p.container);
// close show dialog first
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
// hide and reset changelog table
$dialog.find('div.notfound-message').remove();
$dialog.find('.changelog-table').show().children('tbody')
.html('<tr><td colspan="4"><span class="loading">' + rcmail.gettext('loading') + '</span></td></tr>');
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: p.title,
open: function() {
$dialog.attr('aria-hidden', 'false');
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
},
buttons: [
{
text: rcmail.gettext('close'),
click: function() { $dialog.dialog('close'); },
autofocus: true
}
],
minWidth: 450,
width: 650,
height: 350,
minHeight: 200
})
.show().children('.compare-button').hide();
// initialize event handlers for history dialog UI elements
if (!$dialog.data('initialized')) {
// compare button
$dialog.find('.compare-button input').click(function(e) {
var rev1 = $dialog.find('.changelog-table input.diff-rev1:checked').val(),
rev2 = $dialog.find('.changelog-table input.diff-rev2:checked').val();
if (rev1 && rev2 && rev1 != rev2) {
// swap revisions if the user got it wrong
if (rev1 > rev2) {
var tmp = rev2;
rev2 = rev1;
rev1 = tmp;
}
if (p.comparefunc) {
p.comparefunc(rev1, rev2);
}
}
else {
alert('Invalid selection!')
}
if (!rcube_event.is_keyboard(e) && this.blur) {
this.blur();
}
return false;
});
// delegate handlers for list actions
$dialog.find('.changelog-table tbody').on('click', 'td.actions a', function(e) {
var link = $(this),
action = link.hasClass('restore') ? 'restore' : 'show',
event = $('#eventhistory').data('event'),
rev = link.attr('data-rev');
// ignore clicks on first row (current revision)
if (link.closest('tr').hasClass('first')) {
return false;
}
// let the user confirm the restore action
if (action == 'restore' && !confirm(rcmail.gettext('revisionrestoreconfirm', p.module).replace('$rev', rev))) {
return false;
}
if (p.listfunc) {
p.listfunc(action, rev);
}
if (!rcube_event.is_keyboard(e) && this.blur) {
this.blur();
}
return false;
})
.on('click', 'input.diff-rev1', function(e) {
if (!this.checked) return true;
var rev1 = this.value, selection_valid = false;
$dialog.find('.changelog-table input.diff-rev2').each(function(i, elem) {
$(elem).prop('disabled', elem.value <= rev1);
if (elem.checked && elem.value > rev1) {
selection_valid = true;
}
});
if (!selection_valid) {
$dialog.find('.changelog-table input.diff-rev2:not([disabled])').last().prop('checked', true);
}
});
$dialog.addClass('changelog-dialog').data('initialized', true);
}
return $dialog;
};
// callback from server with changelog data
libkolab_audittrail.render_changelog = function(data, object, folder)
{
var Q = libkolab_audittrail.quote_html;
var $dialog = $('.changelog-dialog')
if (data === false || !data.length) {
return false;
}
var i, change, accessible, op_append,
first = data.length - 1, last = 0,
is_writeable = !!folder.editable,
op_labels = {
RECEIVE: 'actionreceive',
APPEND: 'actionappend',
MOVE: 'actionmove',
DELETE: 'actiondelete',
READ: 'actionread',
FLAGSET: 'actionflagset',
FLAGCLEAR: 'actionflagclear'
},
actions = '<a href="#show" class="iconbutton preview" title="'+ rcmail.gettext('showrevision','libkolab') +'" data-rev="{rev}" /> ' +
(is_writeable ? '<a href="#restore" class="iconbutton restore" title="'+ rcmail.gettext('restore','libkolab') + '" data-rev="{rev}" />' : ''),
tbody = $dialog.find('.changelog-table tbody').html('');
for (i=first; i >= 0; i--) {
change = data[i];
accessible = change.date && change.user;
if (change.op == 'MOVE' && change.mailbox) {
op_append = ' ⇢ ' + change.mailbox;
}
else if ((change.op == 'FLAGSET' || change.op == 'FLAGCLEAR') && change.flags) {
op_append = ': ' + change.flags;
}
else {
op_append = '';
}
$('<tr class="' + (i == first ? 'first' : (i == last ? 'last' : '')) + (accessible ? '' : 'undisclosed') + '">')
.append('<td class="diff">' + (accessible && change.op != 'DELETE' ?
'<input type="radio" name="rev1" class="diff-rev1" value="' + change.rev + '" title="" '+ (i == last ? 'checked="checked"' : '') +' /> '+
'<input type="radio" name="rev2" class="diff-rev2" value="' + change.rev + '" title="" '+ (i == first ? 'checked="checked"' : '') +' /></td>'
: ''))
.append('<td class="revision">' + Q(i+1) + '</td>')
.append('<td class="date">' + Q(change.date || '') + '</td>')
.append('<td class="user">' + Q(change.user || 'undisclosed') + '</td>')
.append('<td class="operation" title="' + op_append + '">' + Q(rcmail.gettext(op_labels[change.op] || '', 'libkolab') + op_append) + '</td>')
.append('<td class="actions">' + (accessible && change.op != 'DELETE' ? actions.replace(/\{rev\}/g, change.rev) : '') + '</td>')
.appendTo(tbody);
}
if (first > 0) {
$dialog.find('.compare-button').fadeIn(200);
$dialog.find('.changelog-table tr.last input.diff-rev1').click();
}
// set dialog size according to content
libkolab_audittrail.dialog_resize($dialog.get(0), $dialog.height() + 15, 600);
return $dialog;
};
// resize and reposition (center) the dialog window
libkolab_audittrail.dialog_resize = function(id, height, width)
{
var win = $(window), w = win.width(), h = win.height();
- $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) })
- .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
+ $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) });
};
// register handlers for mail message history
window.rcmail && rcmail.addEventListener('init', function(e) {
var loading_lock;
if (rcmail.env.task == 'mail') {
rcmail.register_command('kolab-mail-history', function() {
var dialog, uid = rcmail.get_single_uid(), rec = { uid: uid, mbox: rcmail.get_message_mailbox(uid) };
if (!uid || !window.libkolab_audittrail) {
return false;
}
// render dialog
$dialog = libkolab_audittrail.object_history_dialog({
module: 'libkolab',
container: '#mailmessagehistory',
title: rcmail.gettext('objectchangelog','libkolab')
});
$dialog.data('rec', rec);
// fetch changelog data
loading_lock = rcmail.set_busy(true, 'loading', loading_lock);
rcmail.http_post('plugin.message-changelog', { _uid: rec.uid, _mbox: rec.mbox }, loading_lock);
}, rcmail.env.action == 'show');
rcmail.addEventListener('plugin.message_render_changelog', function(data) {
var $dialog = $('#mailmessagehistory'),
rec = $dialog.data('rec');
if (data === false || !data.length || !rec) {
// display 'unavailable' message
$('<div class="notfound-message dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','libkolab') + '</div>')
.insertBefore($dialog.find('.changelog-table').hide());
return;
}
data.module = 'libkolab';
libkolab_audittrail.render_changelog(data, rec, {});
});
rcmail.env.message_commands.push('kolab-mail-history');
}
});
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 4b02faad..15874559 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -1,3456 +1,3455 @@
/**
* Client scripts for the Tasklist plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
*
* This program 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.
*
* This program 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
function rcube_tasklist_ui(settings)
{
// extend base class
rcube_libcalendaring.call(this, settings);
/* constants */
var FILTER_MASK_ALL = 0;
var FILTER_MASK_TODAY = 1;
var FILTER_MASK_TOMORROW = 2;
var FILTER_MASK_WEEK = 4;
var FILTER_MASK_LATER = 8;
var FILTER_MASK_NODATE = 16;
var FILTER_MASK_OVERDUE = 32;
var FILTER_MASK_FLAGGED = 64;
var FILTER_MASK_COMPLETE = 128;
var FILTER_MASK_ASSIGNED = 256;
var FILTER_MASK_MYTASKS = 512;
var filter_masks = {
all: FILTER_MASK_ALL,
today: FILTER_MASK_TODAY,
tomorrow: FILTER_MASK_TOMORROW,
week: FILTER_MASK_WEEK,
later: FILTER_MASK_LATER,
nodate: FILTER_MASK_NODATE,
overdue: FILTER_MASK_OVERDUE,
flagged: FILTER_MASK_FLAGGED,
complete: FILTER_MASK_COMPLETE,
assigned: FILTER_MASK_ASSIGNED,
mytasks: FILTER_MASK_MYTASKS
};
/* private vars */
var tagsfilter = [];
var filtermask = FILTER_MASK_ALL;
var loadstate = { filter:-1, lists:'', search:null };
var idcount = 0;
var focusview = false;
var focusview_lists = [];
var saving_lock;
var ui_loading;
var taskcounts = {};
var listindex = [];
var listdata = {};
var tags = [];
var draghelper;
var search_request;
var search_query;
var completeness_slider;
var task_draghelper;
var tag_draghelper;
var task_drag_active = false;
var list_scroll_top = 0;
var scroll_delay = 400;
var scroll_step = 5;
var scroll_speed = 20;
var scroll_sensitivity = 40;
var scroll_timer;
var tasklists_widget;
var focused_task;
var focused_subclass;
var task_attendees = [];
var attendees_list;
var me = this;
// general datepicker settings
var datepicker_settings = {
// translate from PHP format to datepicker format
dateFormat: settings['date_format'].replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
firstDay : settings['first_day'],
// dayNamesMin: settings['days_short'],
// monthNames: settings['months'],
// monthNamesShort: settings['months'],
changeMonth: false,
showOtherMonths: true,
selectOtherMonths: true
};
var extended_datepicker_settings;
/* public members */
this.tasklists = rcmail.env.tasklists;
this.selected_task = null;
this.selected_list = null;
/* public methods */
this.init = init;
this.edit_task = task_edit_dialog;
this.print_tasks = print_tasks;
this.delete_task = delete_task;
this.add_childtask = add_childtask;
this.quicksearch = quicksearch;
this.reset_search = reset_search;
this.expand_collapse = expand_collapse;
this.list_delete = list_delete;
this.list_remove = list_remove;
this.list_showurl = list_showurl;
this.list_edit_dialog = list_edit_dialog;
this.unlock_saving = unlock_saving;
/* imports */
var Q = this.quote_html;
var text2html = this.text2html;
var event_date_text = this.event_date_text;
var parse_datetime = this.parse_datetime;
var date2unixtime = this.date2unixtime;
var fromunixtime = this.fromunixtime;
var render_message_links = this.render_message_links;
/**
* initialize the tasks UI
*/
function init()
{
if (rcmail.env.action == 'print' && rcmail.task == 'tasks') {
filtermask = rcmail.env.filtermask;
data_ready({data: rcmail.env.tasks});
return;
}
// initialize task list selectors
for (var id in me.tasklists) {
if (settings.selected_list && me.tasklists[settings.selected_list] && !me.tasklists[settings.selected_list].active) {
me.tasklists[settings.selected_list].active = true;
me.selected_list = settings.selected_list;
$(rcmail.gui_objects.tasklistslist).find("input[value='"+settings.selected_list+"']").prop('checked', true);
}
if (me.tasklists[id].editable && (!me.selected_list || me.tasklists[id]['default'] || (me.tasklists[id].active && !me.tasklists[me.selected_list].active))) {
me.selected_list = id;
}
}
// initialize treelist widget that controls the tasklists list
var widget_class = window.kolab_folderlist || rcube_treelist_widget;
tasklists_widget = new widget_class(rcmail.gui_objects.tasklistslist, {
id_prefix: 'rcmlitasklist',
selectable: true,
save_state: true,
keyboard: false,
searchbox: '#tasklistsearch',
search_action: 'tasks/tasklist',
search_sources: [ 'folders', 'users' ],
search_title: rcmail.gettext('listsearchresults','tasklist')
});
tasklists_widget.addEventListener('select', function(node) {
var id = $(this).data('id');
rcmail.enable_command('list-edit', has_permission(me.tasklists[node.id], 'wa'));
rcmail.enable_command('list-delete', has_permission(me.tasklists[node.id], 'xa'));
rcmail.enable_command('list-import', has_permission(me.tasklists[node.id], 'i'));
rcmail.enable_command('list-remove', me.tasklists[node.id] && me.tasklists[node.id].removable);
rcmail.enable_command('list-showurl', me.tasklists[node.id] && !!me.tasklists[node.id].caldavurl);
me.selected_list = node.id;
});
tasklists_widget.addEventListener('subscribe', function(p) {
var list;
if ((list = me.tasklists[p.id])) {
list.subscribed = p.subscribed || false;
rcmail.http_post('tasklist', { action:'subscribe', l:{ id:p.id, active:list.active?1:0, permanent:list.subscribed?1:0 } });
}
});
tasklists_widget.addEventListener('remove', function(p) {
if (me.tasklists[p.id] && me.tasklists[p.id].removable) {
list_remove(p.id);
}
});
tasklists_widget.addEventListener('insert-item', function(p) {
var list = p.data;
if (list && list.id && !list.virtual) {
me.tasklists[list.id] = list;
var prop = { id:p.id, active:list.active?1:0 };
if (list.subscribed) prop.permanent = 1;
rcmail.http_post('tasklist', { action:'subscribe', l:prop });
list_tasks();
$(p.item).data('type', 'tasklist');
}
});
tasklists_widget.addEventListener('search-complete', function(data) {
if (data.length)
rcmail.display_message(rcmail.gettext('nrtasklistsfound','tasklist').replace('$nr', data.length), 'voice');
else
rcmail.display_message(rcmail.gettext('notasklistsfound','tasklist'), 'info');
});
// init (delegate) event handler on tasklist checkboxes
tasklists_widget.container.on('click', 'input[type=checkbox]', function(e) {
var list, id = this.value;
if ((list = me.tasklists[id])) {
list.active = this.checked;
fetch_counts();
if (!this.checked) remove_tasks(id);
else list_tasks(null);
rcmail.http_post('tasklist', { action:'subscribe', l:{ id:id, active:list.active?1:0 } });
// disable focusview
if (!this.checked && focusview && $.inArray(id, focusview_lists) >= 0) {
set_focusview(null);
}
// adjust checked state of original list item
if (tasklists_widget.is_search()) {
tasklists_widget.container.find('input[value="'+id+'"]').prop('checked', this.checked);
}
}
e.stopPropagation();
})
.on('keypress', 'input[type=checkbox]', function(e) {
// select tasklist on <Enter>
if (e.keyCode == 13) {
tasklists_widget.select(this.value);
return rcube_event.cancel(e);
}
})
.find('li:not(.virtual)').data('type', 'tasklist');
// handler for clicks on quickview buttons
tasklists_widget.container.on('click', '.quickview', function(e){
var id = $(this).closest('li').attr('id').replace(/^rcmlitasklist/, '');
if (tasklists_widget.is_search())
id = id.replace(/--xsR$/, '');
if (!rcube_event.is_keyboard(e) && this.blur)
this.blur();
set_focusview(id, e.shiftKey || e.metaKey || e.ctrlKey);
e.stopPropagation();
return false;
});
// register dbl-click handler to open calendar edit dialog
tasklists_widget.container.on('dblclick', ':not(.virtual) > .tasklist', function(e){
var id = $(this).closest('li').attr('id').replace(/^rcmlitasklist/, '');
if (tasklists_widget.is_search())
id = id.replace(/--xsR$/, '');
list_edit_dialog(id);
});
if (me.selected_list) {
rcmail.enable_command('addtask', true);
tasklists_widget.select(me.selected_list);
}
// register server callbacks
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.update_task', update_taskitem);
rcmail.addEventListener('plugin.refresh_tasks', function(p) { update_taskitem(p, true); });
rcmail.addEventListener('plugin.update_counts', update_counts);
rcmail.addEventListener('plugin.insert_tasklist', insert_list);
rcmail.addEventListener('plugin.update_tasklist', update_list);
rcmail.addEventListener('plugin.destroy_tasklist', destroy_list);
rcmail.addEventListener('plugin.unlock_saving', unlock_saving);
rcmail.addEventListener('plugin.refresh_tagcloud', function() { update_tagcloud(); });
rcmail.addEventListener('requestrefresh', before_refresh);
rcmail.addEventListener('plugin.reload_data', function(){
list_tasks(null, true);
setTimeout(fetch_counts, 200);
});
rcmail.addEventListener('plugin.task_render_changelog', task_render_changelog);
rcmail.addEventListener('plugin.task_show_diff', task_show_diff);
rcmail.addEventListener('plugin.task_show_revision', function(data){ task_show_dialog(null, data, true); });
rcmail.addEventListener('plugin.close_history_dialog', close_history_dialog);
rcmail.register_command('list-sort', list_set_sort, true);
rcmail.register_command('list-order', list_set_order, (settings.sort_col || 'auto') != 'auto');
rcmail.register_command('task-history', task_history_dialog, false);
$('#taskviewsortmenu .by-' + (settings.sort_col || 'auto')).attr('aria-checked', 'true').addClass('selected');
$('#taskviewsortmenu .sortorder.' + (settings.sort_order || 'asc')).attr('aria-checked', 'true').addClass('selected');
// start loading tasks
fetch_counts();
list_tasks(settings.selected_filter);
// register event handlers for UI elements
$('#taskselector a').click(function(e) {
if (!$(this).parent().hasClass('inactive')) {
var selector = this.href.replace(/^.*#/, ''),
mask = filter_masks[selector],
shift = e.shiftKey || e.ctrlKey || e.metaKey;
if (!shift)
filtermask = mask; // reset selection on regular clicks
else if (filtermask & mask)
filtermask -= mask;
else
filtermask |= mask;
list_tasks();
}
return false;
});
// quick-add a task
$(rcmail.gui_objects.quickaddform).submit(function(e){
var tasktext = this.elements.text.value,
rec = { id:-(++idcount), title:tasktext, readonly:true, mask:0, complete:0 };
if (tasktext && tasktext.length) {
save_task({ tempid:rec.id, raw:tasktext, list:me.selected_list }, 'new');
render_task(rec);
$('#listmessagebox').hide();
}
// clear form
this.reset();
return false;
}).find('input[type=text]').placeholder(rcmail.gettext('createnewtask','tasklist'));
// click-handler on tags list
$(rcmail.gui_objects.tagslist).on('click', 'li', function(e){
var item = e.target.nodeName == 'LI' ? $(e.target) : $(e.target).closest('li'),
tag = item.data('value');
if (!tag)
return false;
// reset selection on regular clicks
var index = $.inArray(tag, tagsfilter);
var shift = e.shiftKey || e.ctrlKey || e.metaKey;
if (!shift) {
if (tagsfilter.length > 1)
index = -1;
$('li', rcmail.gui_objects.tagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
item.removeClass('selected').attr('aria-checked', 'false');
var a = tagsfilter.slice(0,index);
tagsfilter = a.concat(tagsfilter.slice(index+1));
}
list_tasks();
// clear text selection in IE after shift+click
if (shift && document.selection)
document.selection.empty();
e.preventDefault();
return false;
})
.on('keypress', 'li', function(e) {
if (e.keyCode == 13) {
$(this).trigger('click', { pointerType:'keyboard' });
}
})
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
return false;
});
// click-handler on task list items (delegate)
$(rcmail.gui_objects.resultlist).on('click', function(e){
var item = $(e.target);
var className = e.target.className;
if (item.hasClass('childtoggle')) {
item = item.parent().find('.taskhead');
className = 'childtoggle';
}
else if (!item.hasClass('taskhead'))
item = item.closest('div.taskhead');
// ignore
if (!item.length)
return false;
var id = item.data('id'),
li = item.parent(),
rec = listdata[id];
switch (className) {
case 'childtoggle':
rec.collapsed = !rec.collapsed;
li.children('.childtasks:first').toggle().attr('aria-hidden', rec.collapsed ? 'true' : 'false');
$(e.target).toggleClass('collapsed').html(rec.collapsed ? '&#9654;' : '&#9660;');
rcmail.http_post('tasks/task', { action:'collapse', t:{ id:rec.id, list:rec.list }, collapsed:rec.collapsed?1:0 });
if (e.shiftKey) // expand/collapse all childs
li.children('.childtasks:first .childtoggle.'+(rec.collapsed?'expanded':'collapsed')).click();
break;
case 'complete':
if (rcmail.busy)
return false;
save_task_confirm(rec, 'edit', { _status_before:rec.status + '', status:e.target.checked ? 'COMPLETED' : (rec.complete > 0 ? 'IN-PROCESS' : 'NEEDS-ACTION') });
item.toggleClass('complete');
return true;
case 'flagged':
if (rcmail.busy)
return false;
rec.flagged = rec.flagged ? 0 : 1;
item.toggleClass('flagged').find('.flagged:first').attr('aria-checked', (rec.flagged ? 'true' : 'false'));
save_task(rec, 'edit');
break;
case 'date':
if (rcmail.busy)
return false;
var link = $(e.target).html(''),
input = $('<input type="text" size="10" />').appendTo(link).val(rec.date || '')
input.datepicker($.extend({
onClose: function(dateText, inst) {
if (dateText != (rec.date || '')) {
save_task_confirm(rec, 'edit', { date:dateText });
}
input.datepicker('destroy').remove();
link.html(dateText || rcmail.gettext('nodate','tasklist'));
}
}, extended_datepicker_settings)
)
.datepicker('setDate', rec.date)
.datepicker('show');
break;
case 'delete':
delete_task(id);
break;
case 'actions':
var pos, ref = $(e.target),
menu = $('#taskitemmenu');
if (menu.is(':visible') && menu.data('refid') == id) {
rcmail.command('menu-close', 'taskitemmenu');
}
else {
rcmail.enable_command('task-history', me.tasklists[rec.list] && !!me.tasklists[rec.list].history);
rcmail.command('menu-open', { menu: 'taskitemmenu', show: true }, e.target, e);
menu.data('refid', id);
me.selected_task = rec;
}
e.bubble = false;
break;
case 'extlink':
return true;
default:
if (e.target.nodeName != 'INPUT')
task_show_dialog(id);
break;
}
return false;
})
.on('dblclick', '.taskhead, .childtoggle', function(e){
var id, rec, item = $(e.target);
if (!item.hasClass('taskhead'))
item = item.closest('div.taskhead');
if (!rcmail.busy && item.length && (id = item.data('id')) && (rec = listdata[id])) {
var list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] : {};
if (rec.readonly || !list.editable)
task_show_dialog(id);
else
task_edit_dialog(id, 'edit');
clearSelection();
}
})
.on('keydown', '.taskhead', function(e) {
if (e.target.nodeName == 'INPUT' && e.target.type == 'text')
return true;
var inc = 1;
switch (e.keyCode) {
case 13: // Enter
$(e.target).trigger('click', { pointerType:'keyboard' });
return rcube_event.cancel(e);
case 38: // Up arrow key
inc = -1;
case 40: // Down arrow key
if ($(e.target).hasClass('actions')) {
// unfold actions menu
$(e.target).trigger('click', { pointerType:'keyboard' });
return rcube_event.cancel(e);
}
// focus next/prev task item
var x = 0, target = this, items = $(rcmail.gui_objects.resultlist).find('.taskhead:visible');
items.each(function(i, item) {
if (item === target) {
x = i;
return false;
}
});
items.get(x + inc).focus();
return rcube_event.cancel(e);
case 37: // Left arrow key
case 39: // Right arrow key
$(this).parent().children('.childtoggle:visible').first().trigger('click', { pointerType:'keyboard' });
break;
}
})
.on('focusin', '.taskhead', function(e){
if (rcube_event.is_keyboard(e)) {
var item = $(e.target);
if (!item.hasClass('taskhead'))
item = item.closest('div.taskhead');
var id = item.data('id');
if (id && listdata[id]) {
focused_task = id;
focused_subclass = item.get(0) !== e.target ? e.target.className : null;
}
}
})
.on('focusout', '.taskhead', function(e){
var item = $(e.target);
if (focused_task && item.data('id') == focused_task) {
focused_task = focused_subclass = null;
}
});
/**
*
*/
function task_rsvp(response, delegate)
{
if (me.selected_task && me.selected_task.attendees && response) {
// bring up delegation dialog
if (response == 'delegated' && !delegate) {
rcube_libcalendaring.itip_delegate_dialog(function(data) {
$('#reply-comment-task-rsvp').val(data.comment);
data.rsvp = data.rsvp ? 1 : '';
task_rsvp('delegated', data);
});
return;
}
// update attendee status
for (var data, i=0; i < me.selected_task.attendees.length; i++) {
data = me.selected_task.attendees[i];
if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
data.status = response.toUpperCase();
if (data.status == 'DELEGATED') {
data['delegated-to'] = delegate.to;
}
else {
delete data.rsvp; // unset RSVP flag
if (data['delegated-to']) {
delete data['delegated-to'];
if (data.role == 'NON-PARTICIPANT' && data.status != 'DECLINED') {
data.role = 'REQ-PARTICIPANT';
}
}
}
}
}
// submit status change to server
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasks/task', {
action: 'rsvp',
t: $.extend({}, me.selected_task, (delegate || {})),
filter: filtermask,
status: response,
noreply: $('#noreply-task-rsvp:checked').length ? 1 : 0,
comment: $('#reply-comment-task-rsvp').val()
});
task_show_dialog(me.selected_task.id);
}
}
// init RSVP widget
$('#task-rsvp input.button').click(function(e) {
task_rsvp($(this).attr('rel'))
});
// register click handler for message links
$('#task-links, #taskedit-links').on('click', 'li a.messagelink', function(e) {
rcmail.open_window(this.href);
return false;
});
// register click handler for message delete buttons
$('#taskedit-links').on('click', 'li a.delete', function(e) {
remove_link(e.target);
return false;
});
// extended datepicker settings
var extended_datepicker_settings = $.extend({
showButtonPanel: true,
beforeShow: function(input, inst) {
setTimeout(function(){
$(input).datepicker('widget').find('button.ui-datepicker-close')
.html(rcmail.gettext('nodate','tasklist'))
.attr('onclick', '')
.unbind('click')
.bind('click', function(e){
$(input).datepicker('setDate', null).datepicker('hide');
});
}, 1);
}
}, datepicker_settings);
}
/**
* initialize task edit form elements
*/
function init_taskedit()
{
$('#taskedit').tabs({
activate: function(event, ui) {
// reset autocompletion on tab change (#3389)
if (ui.oldPanel.selector == '#taskedit-panel-attendees') {
rcmail.ksearch_blur();
}
}
});
var completeness_slider_change = function(e, ui){
var v = completeness_slider.slider('value');
if (v >= 98) v = 100;
if (v <= 2) v = 0;
$('#taskedit-completeness').val(v);
};
completeness_slider = $('#taskedit-completeness-slider').slider({
range: 'min',
animate: 'fast',
slide: completeness_slider_change,
change: completeness_slider_change
});
$('#taskedit-completeness').change(function(e){
completeness_slider.slider('value', parseInt(this.value))
});
// register events on alarms and recurrence fields
me.init_alarms_edit('#taskedit-alarms');
me.init_recurrence_edit('#eventedit');
$('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
$('a.edit-nodate').click(function(){
var sel = $(this).attr('rel');
if (sel) $(sel).val('');
return false;
});
// init attendees autocompletion
var ac_props;
// parallel autocompletion
if (rcmail.env.autocomplete_threads > 0) {
ac_props = {
threads: rcmail.env.autocomplete_threads,
sources: rcmail.env.autocomplete_sources
};
}
rcmail.init_address_input_events($('#edit-attendee-name'), ac_props);
rcmail.addEventListener('autocomplete_insert', function(e) {
var success = false;
if (e.field.name == 'participant') {
success = add_attendees(e.insert, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:(e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL') });
}
if (e.field && success) {
e.field.value = '';
}
});
$('#edit-attendee-add').click(function() {
var input = $('#edit-attendee-name');
rcmail.ksearch_blur();
if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) {
input.val('');
}
});
// handle change of "send invitations" checkbox
$('#edit-attendees-invite').change(function() {
$('#edit-attendees-donotify,input.edit-attendee-reply').prop('checked', this.checked);
// hide/show comment field
$('#taskeditform .attendees-commentbox')[this.checked ? 'show' : 'hide']();
});
// delegate change task to "send invitations" checkbox
$('#edit-attendees-donotify').change(function() {
$('#edit-attendees-invite').click();
return false;
});
// configure drop-down menu on time input fields based on jquery UI autocomplete
$('#taskedit-starttime, #taskedit-time, #taskedit input.edit-alarm-time').each(function() {
me.init_time_autocomplete(this, {container: '#taskedit'});
});
}
/**
* Check permissions on the given list object
*/
function has_permission(list, perm)
{
// multiple chars means "either of"
if (String(perm).length > 1) {
for (var i=0; i < perm.length; i++) {
if (has_permission(list, perm[i])) {
return true;
}
}
}
if (list.rights && String(list.rights).indexOf(perm) >= 0) {
return true;
}
return (perm == 'i' && list.editable);
}
/**
* Request counts from the server
*/
function fetch_counts()
{
var active = active_lists();
if (active.length)
rcmail.http_request('counts', { lists:active.join(',') });
else
update_counts({});
}
/**
* List tasks matching the given selector
*/
function list_tasks(sel, force)
{
if (rcmail.busy)
return;
if (sel && filter_masks[sel] !== undefined) {
filtermask = filter_masks[sel];
}
var active = active_lists(),
basefilter = filtermask & FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL,
reload = force || active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query;
if (active.length && reload) {
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('fetch', { filter:basefilter, lists:active.join(','), q:search_query }, true);
}
else if (reload)
data_ready({ data:[], lists:'', filter:basefilter, search:search_query });
else
render_tasklist();
$('#taskselector li.selected').removeClass('selected').attr('aria-checked', 'false');
// select all active selectors
if (filtermask > 0) {
$.each(filter_masks, function(sel, mask) {
if (filtermask & mask)
$('#taskselector li.'+sel).addClass('selected').attr('aria-checked', 'true');
});
}
else
$('#taskselector li.all').addClass('selected').attr('aria-checked', 'true');
}
/**
* Remove all tasks of the given list from the UI
*/
function remove_tasks(list_id)
{
// remove all tasks of the given list from index
var newindex = $.grep(listindex, function(id, i){
return listdata[id] && listdata[id].list != list_id;
});
listindex = newindex;
render_tasklist();
// avoid reloading
me.tasklists[list_id].active = false;
loadstate.lists = active_lists();
}
// open a tasks export dialog
this.export_tasks = function()
{
// close show dialog first
var $dialog = $("#tasksexport"),
form = rcmail.gui_objects.exportform,
buttons = {};
if (!form)
return;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
$("#task-export-list").val('');
buttons[rcmail.gettext('export', 'tasklist')] = function() {
var data = {},
source = $('#task-export-list option:selected').val(),
form_elements = $('select, input', form);
// "current view" export, use hidden form to POST task IDs
if (source === '') {
var cache = {}, tasks = [], inputs = [],
postform = $('#tasks-export-form-post');
$.each(listindex || [], function() {
var rec = listdata[this];
if (match_filter(rec, cache)) {
tasks.push(rec.id);
}
});
// copy form inputs, there may be controls added by other plugins
form_elements.each(function() {
if (this.type != 'checkbox' || this.checked)
inputs.push($('<input>').attr({type: 'hidden', name: this.name, value: this.value}));
});
inputs.push($('<input>').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token}));
inputs.push($('<input>').attr({type: 'hidden', name: 'id', value: tasks.join(',')}));
if (!postform.length)
postform = $('<form>')
.attr({style: 'display: none', method: 'POST', action: '?_task=tasks&_action=export'})
.appendTo('body');
postform.html('').append(inputs).submit();
}
// otherwise we can use simple GET
else {
form_elements.each(function() {
if (this.type != 'checkbox' || this.checked)
data[this.name] = $(this).val();
});
rcmail.goto_url('export', data);
}
$dialog.dialog("close");
};
buttons[rcmail.gettext('cancel', 'tasklist')] = function() {
$dialog.dialog("close");
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: false,
closeOnEscape: false,
title: rcmail.gettext('exporttitle', 'tasklist'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
$dialog.dialog("destroy").hide();
},
buttons: buttons,
width: 520
}).show();
};
/*
// download the selected task as iCal
this.task_download = function(task)
{
if (task && task.id) {
rcmail.goto_url('export', {source: task.list, id: task.id, attachments: 1});
}
};
*/
/**
* Modify query parameters for refresh requests
*/
function before_refresh(query)
{
query.filter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL;
query.lists = active_lists().join(',');
if (search_query)
query.q = search_query;
return query;
}
/**
* Callback if task data from server is ready
*/
function data_ready(response)
{
listdata = {};
listindex = [];
loadstate.lists = response.lists;
loadstate.filter = response.filter;
loadstate.search = response.search;
for (var id, i=0; i < response.data.length; i++) {
id = response.data[i].id;
listindex.push(id);
listdata[id] = response.data[i];
listdata[id].children = [];
// register a forward-pointer to child tasks
if (listdata[id].parent_id && listdata[listdata[id].parent_id])
listdata[listdata[id].parent_id].children.push(id);
}
// sort index before rendering
listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
append_tags(response.tags || []);
render_tasklist();
// show selected task dialog
if (settings.selected_id) {
if (listdata[settings.selected_id]) {
task_show_dialog(settings.selected_id);
delete settings.selected_id;
}
// remove _id from window location
if (window.history.replaceState) {
window.history.replaceState({}, document.title, rcmail.url('', { _list: me.selected_list }));
}
}
rcmail.set_busy(false, 'loading', ui_loading);
}
/**
*
*/
function render_tasklist()
{
// clear display
var id, rec,
count = 0,
cache = {},
activetags = {},
msgbox = $('#listmessagebox').hide(),
list = $(rcmail.gui_objects.resultlist).html('');
for (var i=0; i < listindex.length; i++) {
id = listindex[i];
rec = listdata[id];
if (match_filter(rec, cache)) {
if (rcmail.env.action == 'print') {
render_task_printmode(rec);
continue;
}
render_task(rec);
count++;
// keep a list of tags from all visible tasks
for (var t, j=0; rec.tags && j < rec.tags.length; j++) {
t = rec.tags[j];
if (typeof activetags[t] == 'undefined')
activetags[t] = 0;
activetags[t]++;
}
}
}
if (rcmail.env.action == 'print')
return;
fix_tree_toggles();
update_tagcloud(activetags);
if (!count) {
msgbox.html(rcmail.gettext('notasksfound','tasklist')).show();
rcmail.display_message(rcmail.gettext('notasksfound','tasklist'), 'voice');
}
}
/**
* Show/hide child toggle buttons on all visible task items
*/
function fix_tree_toggles()
{
$('.taskitem', rcmail.gui_objects.resultlist).each(function(i,elem){
var li = $(elem),
rec = listdata[li.attr('rel')],
childs = $('.childtasks li', li);
$('.childtoggle', li)[(childs.length ? 'show' : 'hide')]();
})
}
/**
* Expand/collapse all task items with childs
*/
function expand_collapse(expand)
{
var collapsed = !expand;
$('.taskitem .childtasks')[(collapsed ? 'hide' : 'show')]();
$('.taskitem .childtoggle')
.removeClass(collapsed ? 'expanded' : 'collapsed')
.addClass(collapsed ? 'collapsed' : 'expanded')
.html(collapsed ? '&#9654;' : '&#9660;');
// store new toggle collapse states
var ids = [];
for (var id in listdata) {
if (listdata[id].children && listdata[id].children.length)
ids.push(id);
}
if (ids.length) {
rcmail.http_post('tasks/task', { action:'collapse', t:{ id:ids.join(',') }, collapsed:collapsed?1:0 });
}
}
/**
*
*/
function append_tags(taglist)
{
// find new tags
var newtags = [];
for (var i=0; i < taglist.length; i++) {
if ($.inArray(taglist[i], tags) < 0)
newtags.push(taglist[i]);
}
tags = tags.concat(newtags);
// append new tags to tag cloud
$.each(newtags, function(i, tag){
$('<li role="checkbox" aria-checked="false" tabindex="0"></li>')
.attr('rel', tag)
.data('value', tag)
.html(Q(tag) + '<span class="count"></span>')
.appendTo(rcmail.gui_objects.tagslist)
.draggable({
addClasses: false,
revert: 'invalid',
revertDuration: 300,
helper: tag_draggable_helper,
start: tag_draggable_start,
appendTo: 'body',
cursor: 'pointer'
});
});
// re-sort tags list
$(rcmail.gui_objects.tagslist).children('li').sortElements(function(a,b){
return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1;
});
}
/**
* Display the given counts to each tag and set those inactive which don't
* have any matching tasks in the current view.
*/
function update_tagcloud(counts)
{
// compute counts first by iterating over all visible task items
if (typeof counts == 'undefined') {
counts = {};
$('li.taskitem', rcmail.gui_objects.resultlist).each(function(i,li){
var t, id = $(li).attr('rel'),
rec = listdata[id];
for (var j=0; rec && rec.tags && j < rec.tags.length; j++) {
t = rec.tags[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
});
}
$(rcmail.gui_objects.tagslist).children('li').each(function(i,li){
var elem = $(li), tag = elem.attr('rel'),
count = counts[tag] || 0;
elem.children('.count').html(count+'');
if (count == 0) elem.addClass('inactive');
else elem.removeClass('inactive');
});
}
/* Helper functions for drag & drop functionality of tags */
function tag_draggable_helper()
{
if (!tag_draghelper)
tag_draghelper = $('<div class="tag-draghelper"></div>');
else
tag_draghelper.html('');
$(this).clone().addClass('tag').appendTo(tag_draghelper);
return tag_draghelper;
}
function tag_draggable_start(event, ui)
{
$('.taskhead').droppable({
hoverClass: 'droptarget',
accept: tag_droppable_accept,
drop: tag_draggable_dropped,
addClasses: false
});
}
function tag_droppable_accept(draggable)
{
if (rcmail.busy)
return false;
var tag = draggable.data('value'),
drop_id = $(this).data('id'),
drop_rec = listdata[drop_id],
list = drop_rec && me.tasklists[drop_rec.list] ? me.tasklists[drop_rec.list] : { editable:true };
// target is not editable or already has this tag assigned
if (!drop_rec || drop_rec.readonly || !has_permission(list, 'i') || (drop_rec.tags && $.inArray(tag, drop_rec.tags) >= 0)) {
return false;
}
return true;
}
function tag_draggable_dropped(event, ui)
{
var drop_id = $(this).data('id'),
tag = ui.draggable.data('value'),
rec = listdata[drop_id];
if (rec && rec.id) {
if (!rec.tags) rec.tags = [];
rec.tags.push(tag);
save_task(rec, 'edit');
}
}
/**
*
*/
function update_counts(counts)
{
// got new data
if (counts)
taskcounts = counts;
// iterate over all selector links and update counts
$('#taskselector a').each(function(i, elem){
var link = $(elem),
f = link.parent().attr('class').replace(/\s\w+/g, '');
if (f != 'all')
link.children('span').html(taskcounts[f] || '')[(taskcounts[f] ? 'show' : 'hide')]();
});
// spacial case: overdue
$('#taskselector li.overdue')[(taskcounts.overdue ? 'removeClass' : 'addClass')]('inactive');
}
/**
* Callback from server to update a single task item
*/
function update_taskitem(rec, filter)
{
// handle a list of task records
if ($.isArray(rec)) {
$.each(rec, function(i,r){ update_taskitem(r, filter); });
return;
}
var id = rec.id,
oldid = rec.tempid || id,
oldrec = listdata[oldid],
oldindex = $.inArray(oldid, listindex),
oldparent = oldrec ? (oldrec._old_parent_id || oldrec.parent_id) : null,
list = me.tasklists[rec.list];
if (oldindex >= 0)
listindex[oldindex] = id;
else
listindex.push(id);
listdata[id] = rec;
// remove child-pointer from old parent
if (oldparent && listdata[oldparent] && oldparent != rec.parent_id) {
var oldchilds = listdata[oldparent].children,
i = $.inArray(oldid, oldchilds);
if (i >= 0) {
listdata[oldparent].children = oldchilds.slice(0,i).concat(oldchilds.slice(i+1));
}
}
// register a forward-pointer to child tasks
if (rec.parent_id && listdata[rec.parent_id] && listdata[rec.parent_id].children && $.inArray(id, listdata[rec.parent_id].children) < 0)
listdata[rec.parent_id].children.push(id);
// restore pointers to my children
if (!listdata[id].children) {
listdata[id].children = [];
for (var pid in listdata) {
if (listdata[pid].parent_id == id)
listdata[id].children.push(pid);
}
}
// copy _depth property from old rec or derive from parent
if (rec.parent_id && listdata[rec.parent_id]) {
rec._depth = (listdata[rec.parent_id]._depth || 0) + 1;
}
else if (oldrec) {
rec._depth = oldrec._depth || 0;
}
if (list.active || rec.tempid) {
if (!filter || match_filter(rec, {}))
render_task(rec, oldid);
}
else {
$('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).remove();
}
append_tags(rec.tags || []);
update_tagcloud();
fix_tree_toggles();
// refresh currently displayed task details dialog
if ($('#taskshow').is(':visible') && me.selected_task && me.selected_task.id == rec.id) {
task_show_dialog(rec.id);
}
}
/**
* Submit the given (changed) task record to the server
*/
function save_task(rec, action)
{
// show confirmation dialog when status of an assigned task has changed
if (rec._status_before !== undefined && is_attendee(rec))
return save_task_confirm(rec, action);
if (!rcmail.busy) {
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasks/task', { action:action, t:rec, filter:filtermask });
$('button.ui-button:ui-button').button('option', 'disabled', rcmail.busy);
return true;
}
return false;
}
/**
* Display confirm dialog when modifying/deleting a task record
*/
var save_task_confirm = function(rec, action, updates)
{
var data = $.extend({}, rec, updates || {}),
notify = false, partstat = false, html = '',
do_confirm = settings.itip_notify & 2;
// task has attendees, ask whether to notify them
if (has_attendees(rec) && is_organizer(rec)) {
notify = true;
if (do_confirm) {
html = rcmail.gettext('changeconfirmnotifications', 'tasklist');
}
else {
data._notify = settings.itip_notify;
}
}
// ask whether to change my partstat and notify organizer
else if (data._status_before !== undefined && data.status && data._status_before != data.status && is_attendee(rec)) {
partstat = true;
if (do_confirm) {
html = rcmail.gettext('partstatupdatenotification', 'tasklist');
}
else if (settings.itip_notify & 1) {
data._reportpartstat = data.status == 'CANCELLED' ? 'DECLINED' : data.status;
}
}
// remove to avoid endless recursion
delete data._status_before;
// show dialog
if (html) {
var $dialog = $('<div>').html(html);
var buttons = [];
buttons.push({
text: rcmail.gettext('saveandnotify', 'tasklist'),
click: function() {
if (notify) data._notify = 1;
if (partstat) data._reportpartstat = data.status == 'CANCELLED' ? 'DECLINED' : data.status;
save_task(data, action);
$(this).dialog('close');
}
});
buttons.push({
text: rcmail.gettext('save', 'tasklist'),
click: function() {
save_task(data, action);
$(this).dialog('close');
}
});
buttons.push({
text: rcmail.gettext('cancel', 'tasklist'),
click: function() {
$(this).dialog('close');
if (updates)
render_task(rec, rec.id); // restore previous state
}
});
$dialog.dialog({
modal: true,
width: 460,
closeOnEscapeType: false,
dialogClass: 'warning no-close',
title: rcmail.gettext('changetaskconfirm', 'tasklist'),
buttons: buttons,
open: function() {
setTimeout(function(){
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
}, 5);
},
close: function(){
$dialog.dialog('destroy').remove();
}
}).addClass('task-update-confirm').show();
return true;
}
// do update
return save_task(data, action);
}
/**
* Remove saving lock and free the UI for new input
*/
function unlock_saving()
{
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
$('button.ui-button:ui-button').button('option', 'disabled', false);
}
}
/**
* Render the given task into the tasks list
*/
function render_task(rec, replace)
{
var tags_html = '';
for (var j=0; rec.tags && j < rec.tags.length; j++)
tags_html += '<span class="tag">' + Q(rec.tags[j]) + '</span>';
var label_id = rcmail.html_identifier(rec.id) + '-title';
var div = $('<div>').addClass('taskhead').html(
'<div class="progressbar"><div class="progressvalue" style="width:' + (rec.complete * 100) + '%"></div></div>' +
'<input type="checkbox" name="completed[]" value="1" class="complete" aria-label="' + rcmail.gettext('complete','tasklist') + '" ' + (is_complete(rec) ? 'checked="checked" ' : '') + '/>' +
'<span class="flagged" role="checkbox" tabindex="0" aria-checked="' + (rec.flagged ? 'true' : 'false') + '" aria-label="' + rcmail.gettext('flagged','tasklist') + '"></span>' +
'<span class="title" id="' + label_id + '">' + text2html(Q(rec.title)) + '</span>' +
'<span class="tags">' + tags_html + '</span>' +
'<span class="date">' + Q(rec.date || rcmail.gettext('nodate','tasklist')) + '</span>' +
'<a href="#" class="actions" aria-haspopup="true" aria-expanded="false">' + rcmail.gettext('taskactions','tasklist') + '</a>'
)
.attr('tabindex', '0')
.attr('aria-labelledby', label_id)
.data('id', rec.id)
.draggable({
revert: 'invalid',
addClasses: false,
cursorAt: { left:-10, top:12 },
helper: task_draggable_helper,
appendTo: 'body',
start: task_draggable_start,
stop: task_draggable_stop,
drag: task_draggable_move,
revertDuration: 300
});
if (is_complete(rec))
div.addClass('complete');
if (rec.flagged)
div.addClass('flagged');
if (!rec.date)
div.addClass('nodate');
if ((rec.mask & FILTER_MASK_OVERDUE))
div.addClass('overdue');
var li, inplace = false, parent = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : null;
if (replace && (li = $('li[rel="'+replace+'"]', rcmail.gui_objects.resultlist)) && li.length) {
li.children('div.taskhead').first().replaceWith(div);
li.attr('rel', rec.id);
inplace = true;
}
else {
li = $('<li role="treeitem">')
.attr('rel', rec.id)
.addClass('taskitem')
.append((rec.collapsed ? '<span class="childtoggle collapsed">&#9654;' : '<span class="childtoggle expanded">&#9660;') + '</span>')
.append(div)
.append('<ul class="childtasks" role="group" style="' + (rec.collapsed ? 'display:none' : '') + '" aria-hidden="' + (rec.collapsed ? 'true' : 'false') +'"></ul>');
if (!parent || !parent.length)
li.appendTo(rcmail.gui_objects.resultlist);
}
if (!inplace && parent && parent.length)
li.appendTo(parent);
if (replace) {
resort_task(rec, li, true);
// TODO: remove the item after a while if it doesn't match the current filter anymore
}
// re-set focus to taskhead element after DOM update
if (focused_task == rec.id) {
focus_task(li);
}
}
/**
* Render the given task into the tasks list (in print mode)
*/
function render_task_printmode(rec)
{
var label_id = rcmail.html_identifier(rec.id) + '-title',
div = $('<div>').addClass('taskhead')
.append($('<span class="title">').attr('id', label_id).text(rec.title)),
parent = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : null,
li = $('<li role="treeitem">').attr('rel', rec.id).addClass('taskitem')
.append(div)
.append('<ul class="childtasks" role="group"></ul>');
if (rec.description)
div.append($('<span class="description">').text(rec.description));
/*
if (is_complete(rec))
div.addClass('complete');
if (rec.flagged)
div.addClass('flagged');
if (!rec.date)
div.addClass('nodate');
if (rec.mask & FILTER_MASK_OVERDUE)
div.addClass('overdue');
*/
if (!parent || !parent.length)
li.appendTo(rcmail.gui_objects.resultlist);
else
li.appendTo(parent);
}
/**
* Move the given task item to the right place in the list
*/
function resort_task(rec, li, animated)
{
var dir = 0, index, slice, cmp, next_li, next_id, next_rec, insert_after, past_myself;
// animated moving
var insert_animated = function(li, before, after) {
if (before && li.next().get(0) == before.get(0))
return; // nothing to do
else if (after && li.prev().get(0) == after.get(0))
return; // nothing to do
var speed = 300;
li.slideUp(speed, function(){
if (before) li.insertBefore(before);
else if (after) li.insertAfter(after);
li.slideDown(speed, function(){
if (focused_task == rec.id) {
focus_task(li);
}
});
});
}
// remove from list index
var oldlist = listindex.join('%%%');
var oldindex = $.inArray(rec.id, listindex);
if (oldindex >= 0) {
slice = listindex.slice(0,oldindex);
listindex = slice.concat(listindex.slice(oldindex+1));
}
// find the right place to insert the task item
li.parent().children('.taskitem').each(function(i, elem){
next_li = $(elem);
next_id = next_li.attr('rel');
next_rec = listdata[next_id];
if (next_id == rec.id) {
past_myself = true;
return 1; // continue
}
cmp = next_rec ? task_cmp(rec, next_rec) : 0;
if (cmp > 0 || (cmp == 0 && !past_myself)) {
insert_after = next_li;
return 1; // continue;
}
else if (next_li && cmp < 0) {
if (animated) insert_animated(li, next_li);
else li.insertBefore(next_li);
index = $.inArray(next_id, listindex);
return false; // break
}
});
if (insert_after) {
if (animated) insert_animated(li, null, insert_after);
else li.insertAfter(insert_after);
next_id = insert_after.attr('rel');
index = $.inArray(next_id, listindex);
}
// insert into list index
if (next_id && index >= 0) {
slice = listindex.slice(0,index);
slice.push(rec.id);
listindex = slice.concat(listindex.slice(index));
}
else { // restore old list index
listindex = oldlist.split('%%%');
}
}
/**
* Compare function of two task records.
* (used for sorting)
*/
function task_cmp(a, b)
{
// sort by hierarchy level first
if ((a._depth || 0) != (b._depth || 0))
return a._depth - b._depth;
var p, alt, inv = 1, c = is_complete(a) - is_complete(b), d = c;
// completed tasks always move to the end
if (c != 0)
return c;
// custom sorting
if (settings.sort_col && settings.sort_col != 'auto') {
alt = settings.sort_col == 'datetime' || settings.sort_col == 'startdatetime' ? 99999999999 : 0
d = (a[settings.sort_col]||alt) - (b[settings.sort_col]||alt);
inv = settings.sort_order == 'desc' ? -1 : 1;
}
// default sorting (auto)
else {
if (!d) d = (b._hasdate-0) - (a._hasdate-0);
if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
}
// fall-back to created/changed date
if (!d) d = (a.created||0) - (b.created||0);
if (!d) d = (a.changed||0) - (b.changed||0);
return d * inv;
}
/**
* Set focus on the given task item after DOM update
*/
function focus_task(li)
{
var selector = '.taskhead';
if (focused_subclass)
selector += ' .' + focused_subclass
li.find(selector).focus();
}
/**
* Determine whether the given task should be displayed as "complete"
*/
function is_complete(rec)
{
return ((rec.complete == 1.0 && !rec.status) || rec.status === 'COMPLETED') ? 1 : 0;
}
/**
*
*/
function get_all_childs(id)
{
var cid, childs = [];
for (var i=0; listdata[id].children && i < listdata[id].children.length; i++) {
cid = listdata[id].children[i];
childs.push(cid);
childs = childs.concat(get_all_childs(cid));
}
return childs;
}
/* Helper functions for drag & drop functionality */
function task_draggable_helper()
{
if (!task_draghelper)
task_draghelper = $('<div class="taskitem-draghelper">&#x2714;</div>');
return task_draghelper;
}
function task_draggable_start(event, ui)
{
var opts = {
hoverClass: 'droptarget',
accept: task_droppable_accept,
drop: task_draggable_dropped,
addClasses: false
};
$('.taskhead, #rootdroppable').droppable(opts);
tasklists_widget.droppable(opts);
$(this).parent().addClass('dragging');
$('#rootdroppable').show();
// enable auto-scrolling of list container
var container = $(rcmail.gui_objects.resultlist);
if (container.height() > container.parent().height()) {
task_drag_active = true;
list_scroll_top = container.parent().scrollTop();
}
}
function task_draggable_move(event, ui)
{
var scroll = 0,
mouse = rcube_event.get_mouse_pos(event),
container = $(rcmail.gui_objects.resultlist);
mouse.y -= container.parent().offset().top;
if (mouse.y < scroll_sensitivity && list_scroll_top > 0) {
scroll = -1; // up
}
else if (mouse.y > container.parent().height() - scroll_sensitivity) {
scroll = 1; // down
}
if (task_drag_active && scroll != 0) {
if (!scroll_timer)
scroll_timer = window.setTimeout(function(){ tasklist_drag_scroll(container, scroll); }, scroll_delay);
}
else if (scroll_timer) {
window.clearTimeout(scroll_timer);
scroll_timer = null;
}
}
function task_draggable_stop(event, ui)
{
$(this).parent().removeClass('dragging');
$('#rootdroppable').hide();
task_drag_active = false;
}
function task_droppable_accept(draggable)
{
if (rcmail.busy)
return false;
var drag_id = draggable.data('id'),
drop_id = $(this).data('id'),
drag_rec = listdata[drag_id] || {},
drop_rec = listdata[drop_id];
// drop target is another list
if (drag_rec && $(this).data('type') == 'tasklist') {
var drop_list = me.tasklists[drop_id],
from_list = me.tasklists[drag_rec.list];
return !drag_rec.parent_id && drop_id != drag_rec.list && drop_list && drop_list.editable && from_list && from_list.editable;
}
if (drop_rec && drop_rec.list != drag_rec.list)
return false;
if (drop_id == drag_rec.parent_id)
return false;
while (drop_rec && drop_rec.parent_id) {
if (drop_rec.parent_id == drag_id)
return false;
drop_rec = listdata[drop_rec.parent_id];
}
return true;
}
function task_draggable_dropped(event, ui)
{
var drop_id = $(this).data('id'),
task_id = ui.draggable.data('id'),
rec = listdata[task_id],
parent, li;
// dropped on another list -> move
if ($(this).data('type') == 'tasklist') {
if (rec) {
save_task({ id:rec.id, list:drop_id, _fromlist:rec.list }, 'move');
rec.list = drop_id;
}
}
// dropped on a new parent task or root
else {
parent = drop_id ? $('li[rel="'+drop_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : $(rcmail.gui_objects.resultlist)
if (rec && parent.length) {
// submit changes to server
rec._old_parent_id = rec.parent_id;
rec.parent_id = drop_id || 0;
save_task(rec, 'edit');
li = ui.draggable.parent();
li.slideUp(300, function(){
li.appendTo(parent);
resort_task(rec, li);
li.slideDown(300);
fix_tree_toggles();
});
}
}
}
/**
* Scroll list container in the given direction
*/
function tasklist_drag_scroll(container, dir)
{
if (!task_drag_active)
return;
var old_top = list_scroll_top;
container.parent().get(0).scrollTop += scroll_step * dir;
list_scroll_top = container.parent().scrollTop();
scroll_timer = null;
if (list_scroll_top != old_top)
scroll_timer = window.setTimeout(function(){ tasklist_drag_scroll(container, dir); }, scroll_speed);
}
// check if the task has 'real' attendees, excluding the current user
var has_attendees = function(task)
{
return !!(task.attendees && task.attendees.length && (task.attendees.length > 1 || String(task.attendees[0].email).toLowerCase() != settings.identity.email));
};
// check if the current user is an attendee of this task
var is_attendee = function(task, email, role)
{
var i, attendee, emails = email ? ';' + email.toLowerCase() : settings.identity.emails;
for (i=0; task.attendees && i < task.attendees.length; i++) {
attendee = task.attendees[i];
if ((!role || attendee.role == role) && attendee.email && emails.indexOf(';'+attendee.email.toLowerCase()) >= 0) {
return attendee;
}
}
return false;
};
// check if the current user is the organizer
var is_organizer = function(task, email)
{
if (!email) email = task.organizer ? task.organizer.email : null;
if (email)
return settings.identity.emails.indexOf(';'+email) >= 0;
return true;
};
// add the given list of participants
var add_attendees = function(names, params)
{
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
// parse name/email pairs
var i, item, email, name, success = false;
for (i=0; i < names.length; i++) {
email = name = '';
item = $.trim(names[i]);
if (!item.length) {
continue;
}
// address in brackets without name (do nothing)
else if (item.match(/^<[^@]+@[^>]+>$/)) {
email = item.replace(/[<>]/g, '');
}
// address without brackets and without name (add brackets)
else if (rcube_check_email(item)) {
email = item;
}
// address with name
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
email = RegExp.$1;
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
}
if (email) {
add_attendee($.extend({ email:email, name:name }, params));
success = true;
}
else {
alert(rcmail.gettext('noemailwarning'));
}
}
return success;
};
// add the given attendee to the list
var add_attendee = function(data, readonly, before)
{
if (!me.selected_task)
return false;
// check for dupes...
var exists = false;
$.each(task_attendees, function(i, v) { exists |= (v.email == data.email); });
if (exists)
return false;
var dispname = Q(data.name || data.email);
if (data.email)
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
// delete icon
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
var tooltip, status = (data.status || '').toLowerCase(),
status_label = rcmail.gettext('status' + status, 'libcalendaring');
// send invitation checkbox
var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('tasklist.sendinvitations')) + '" '
+ (!data.noreply && settings.itip_notify & 1 ? 'checked="checked" ' : '') + '/>';
if (data['delegated-to'])
tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
else if (data['delegated-from'])
tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
else if (status)
tooltip = status_label;
// add expand button for groups
if (data.cutype == 'GROUP') {
dispname += ' <a href="#expand" data-email="' + Q(data.email) + '" class="iconbutton add expandlink" title="' + rcmail.gettext('expandattendeegroup','libcalendaring') + '">' +
rcmail.gettext('expandattendeegroup','libcalendaring') + '</a>';
}
var html = '<td class="name"><span class="attendee-name">' + dispname + '</span></td>' +
'<td class="confirmstate"><span class="' + status + '" title="' + Q(tooltip) + '">' + Q(status ? status_label : '') + '</span></td>' +
(data.cutype != 'RESOURCE' ? '<td class="invite">' + (readonly || !invbox ? '' : invbox) + '</td>' : '') +
'<td class="options">' + (readonly ? '' : dellink) + '</td>';
var tr = $('<tr>')
.addClass(String(data.role).toLowerCase())
.html(html);
if (before)
tr.insertBefore(before)
else
tr.appendTo(attendees_list);
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
tr.find('a.mailtolink').click(task_attendee_click);
tr.find('a.expandlink').click(data, function(e) { me.expand_attendee_group(e, add_attendee, remove_attendee); return false; });
tr.find('input.edit-attendee-reply').click(function() {
var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
$('#taskeditform .attendees-commentbox')[enabled ? 'show' : 'hide']();
});
task_attendees.push(data);
return true;
};
// event handler for clicks on an attendee link
var task_attendee_click = function(e)
{
var mailto = this.href.substr(7);
rcmail.command('compose', mailto);
return false;
};
// remove an attendee from the list
var remove_attendee = function(elem, id)
{
$(elem).closest('tr').remove();
task_attendees = $.grep(task_attendees, function(data) { return (data.name != id && data.email != id) });
};
/**
* Show task details in a dialog
*/
function task_show_dialog(id, data, temp)
{
var $dialog = $('#taskshow'), rec, list;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
// remove status-* classes
$dialog.removeClass(function(i, oldclass) {
var oldies = String(oldclass).split(' ');
return $.grep(oldies, function(cls) { return cls.indexOf('status-') === 0 }).join(' ');
});
if (!(rec = (data || listdata[id])) || (rcmail.menu_stack && rcmail.menu_stack.length > 0))
return;
me.selected_task = rec;
list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] : {};
// fill dialog data
$('#task-parent-title').html(Q(rec.parent_title || '')+' &raquo;').css('display', rec.parent_title ? 'block' : 'none');
$('#task-title').html(text2html(Q(rec.title || '')));
$('#task-description').html(text2html(rec.description || '', 300, 6))[(rec.description ? 'show' : 'hide')]();
$('#task-date')[(rec.date ? 'show' : 'hide')]().children('.task-text').html(Q(rec.date || rcmail.gettext('nodate','tasklist')));
$('#task-time').html(Q(rec.time || ''));
$('#task-start')[(rec.startdate ? 'show' : 'hide')]().children('.task-text').html(Q(rec.startdate || ''));
$('#task-starttime').html(Q(rec.starttime || ''));
$('#task-alarm')[(rec.alarms_text ? 'show' : 'hide')]().children('.task-text').html(Q(rec.alarms_text));
$('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%');
$('#task-status')[(rec.status ? 'show' : 'hide')]().children('.task-text').html(rcmail.gettext('status-'+String(rec.status).toLowerCase(),'tasklist'));
$('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
$('#task-attendees, #task-organizer, #task-created-changed, #task-rsvp, #task-rsvp-comment').hide();
var itags = get_inherited_tags(rec);
var taglist = $('#task-tags')[(rec.tags && rec.tags.length || itags.length ? 'show' : 'hide')]().children('.task-text').empty();
if (rec.tags && rec.tags.length) {
$.each(rec.tags, function(i,val){
$('<span>').addClass('tag-element').html(Q(val)).appendTo(taglist);
});
}
// append inherited tags
if (itags.length) {
$.each(itags, function(i,val){
if (!rec.tags || $.inArray(val, rec.tags) < 0)
$('<span>').addClass('tag-element inherit').html(Q(val)).appendTo(taglist);
});
// re-sort tags list
$(taglist).children().sortElements(function(a,b){
return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1;
});
}
if (rec.status) {
$dialog.addClass('status-' + String(rec.status).toLowerCase());
}
if (rec.flagged) {
$dialog.addClass('status-flagged');
}
if (rec.recurrence && rec.recurrence_text) {
$('#task-recurrence').show().children('.task-text').html(Q(rec.recurrence_text));
}
else {
$('#task-recurrence').hide();
}
if (rec.created || rec.changed) {
$('#task-created-changed .task-created').html(Q(rec.created_ || rcmail.gettext('unknown','tasklist')))
$('#task-created-changed .task-changed').html(Q(rec.changed_ || rcmail.gettext('unknown','tasklist')))
$('#task-created-changed').show()
}
// build attachments list
$('#task-attachments').hide();
if ($.isArray(rec.attachments)) {
task_show_attachments(rec.attachments || [], $('#task-attachments').children('.task-text'), rec);
if (rec.attachments.length > 0) {
$('#task-attachments').show();
}
}
// build attachments list
$('#task-links').hide();
if ($.isArray(rec.links) && rec.links.length) {
render_message_links(rec.links || [], $('#task-links').children('.task-text'), false, 'tasklist');
$('#task-links').show();
}
// list task attendees
if (list.attendees && rec.attendees) {
/*
// sort resources to the end
rec.attendees.sort(function(a,b) {
var j = a.cutype == 'RESOURCE' ? 1 : 0,
k = b.cutype == 'RESOURCE' ? 1 : 0;
return (j - k);
});
*/
var j, data, rsvp = false, mystatus = null, line, morelink, html = '', overflow = '',
organizer = is_organizer(rec);
for (j=0; j < rec.attendees.length; j++) {
data = rec.attendees[j];
if (data.email && settings.identity.emails.indexOf(';'+data.email) >= 0) {
mystatus = data.status.toLowerCase();
if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
rsvp = mystatus;
}
line = rcube_libcalendaring.attendee_html(data);
if (morelink)
overflow += line;
else
html += line;
// stop listing attendees
if (j == 7 && rec.attendees.length >= 7) {
morelink = $('<a href="#more" class="morelink"></a>').html(rcmail.gettext('andnmore', 'tasklist').replace('$nr', rec.attendees.length - j - 1));
}
}
if (html) {
$('#task-attendees').show()
.children('.task-text')
.html(html)
.find('a.mailtolink').click(task_attendee_click);
// display all attendees in a popup when clicking the "more" link
if (morelink) {
$('#task-attendees .task-text').append(morelink);
morelink.click(function(e) {
rcmail.show_popup_dialog(
'<div id="all-task-attendees" class="task-attendees">' + html + overflow + '</div>',
rcmail.gettext('tabattendees', 'tasklist'),
null,
{width: 450, modal: false}
);
$('#all-task-attendees a.mailtolink').click(task_attendee_click);
return false;
});
}
}
/*
if (mystatus && !rsvp) {
$('#task-partstat').show().children('.changersvp')
.removeClass('accepted tentative declined delegated needs-action')
.addClass(mystatus)
.children('.task-text')
.html(rcmail.gettext('status' + mystatus, 'libcalendaring'));
}
*/
var show_rsvp = !temp && rsvp && list.editable && !is_organizer(rec) && rec.status != 'CANCELLED';
$('#task-rsvp')[(show_rsvp ? 'show' : 'hide')]();
$('#task-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+mystatus+']').prop('disabled', true);
if (show_rsvp && rec.comment) {
$('#task-rsvp-comment').show().children('.task-text').html(Q(rec.comment));
}
$('#task-rsvp a.reply-comment-toggle').show();
$('#task-rsvp .itip-reply-comment textarea').hide().val('');
if (rec.organizer && !organizer) {
$('#task-organizer').show().children('.task-text').html(rcube_libcalendaring.attendee_html($.extend(rec.organizer, { role:'ORGANIZER' })));
}
}
// define dialog buttons
var buttons = [];
if (list.editable && !rec.readonly) {
buttons.push({
text: rcmail.gettext('edit','tasklist'),
click: function() {
task_edit_dialog(me.selected_task.id, 'edit');
},
disabled: rcmail.busy
});
}
if (has_permission(list, 'td') && !rec.readonly) {
buttons.push({
text: rcmail.gettext('delete','tasklist'),
'class': 'delete',
click: function() {
if (delete_task(me.selected_task.id))
$dialog.dialog('close');
},
disabled: rcmail.busy
});
}
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('taskdetails', 'tasklist'),
open: function() {
$dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
},
close: function() {
$dialog.dialog('destroy').appendTo(document.body);
$('.libcal-rsvp-replymode').hide();
},
dragStart: function() {
$('.libcal-rsvp-replymode').hide();
},
resizeStart: function() {
$('.libcal-rsvp-replymode').hide();
},
buttons: buttons,
minWidth: 500,
width: 580
}).show();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
}
/**
*
*/
function task_history_dialog()
{
var dialog, rec = me.selected_task;
if (!rec || !rec.id || !window.libkolab_audittrail) {
return false;
}
// render dialog
$dialog = libkolab_audittrail.object_history_dialog({
module: 'tasklist',
container: '#taskhistory',
title: rcmail.gettext('objectchangelog','tasklist') + ' - ' + rec.title,
// callback function for list actions
listfunc: function(action, rev) {
var rec = $dialog.data('rec');
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('task', { action: action, t: { id: rec.id, list:rec.list, rev: rev } }, saving_lock);
},
// callback function for comparing two object revisions
comparefunc: function(rev1, rev2) {
var rec = $dialog.data('rec');
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('task', { action:'diff', t: { id: rec.id, list: rec.list, rev1: rev1, rev2: rev2 } }, saving_lock);
}
});
$dialog.data('rec', rec);
// fetch changelog data
saving_lock = rcmail.set_busy(true, 'loading', saving_lock);
rcmail.http_post('task', { action: 'changelog', t: { id: rec.id, list: rec.list } }, saving_lock);
}
/**
*
*/
function task_render_changelog(data)
{
var $dialog = $('#taskhistory'),
rec = $dialog.data('rec');
if (data === false || !data.length || !rec) {
// display 'unavailable' message
$('<div class="notfound-message task-dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','tasklist') + '</div>')
.insertBefore($dialog.find('.changelog-table').hide());
return;
}
data.module = 'tasklist';
libkolab_audittrail.render_changelog(data, rec, me.tasklists[rec.list]);
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 600);
}
/**
*
*/
function task_show_diff(data)
{
var rec = me.selected_task,
$dialog = $("#taskdiff");
$dialog.find('div.form-section, h2.task-title-new').hide().data('set', false).find('.index').html('');
$dialog.find('div.form-section.clone').remove();
// always show event title and date
$('.task-title', $dialog).text(rec.title).removeClass('task-text-old').show();
// show each property change
$.each(data.changes, function(i, change) {
var prop = change.property, r2, html = false,
row = $('div.task-' + prop, $dialog).first();
// special case: title
if (prop == 'title') {
$('.task-title', $dialog).addClass('task-text-old').text(change['old'] || '--');
$('.task-title-new', $dialog).text(change['new'] || '--').show();
}
// no display container for this property
if (!row.length) {
return true;
}
// clone row if already exists
if (row.data('set')) {
r2 = row.clone().addClass('clone').insertAfter(row);
row = r2;
}
// render description text
if (prop == 'description') {
if (!change.diff_ && change['old']) change.old_ = text2html(change['old']);
if (!change.diff_ && change['new']) change.new_ = text2html(change['new']);
html = true;
}
// format attendees struct
else if (prop == 'attendees') {
if (change['old']) change.old_ = rcube_libcalendaring.attendee_html(change['old']);
if (change['new']) change.new_ = rcube_libcalendaring.attendee_html($.extend({}, change['old'] || {}, change['new']));
html = true;
}
// localize status
else if (prop == 'status') {
if (change['old']) change.old_ = rcmail.gettext('status-'+String(change['old']).toLowerCase(), 'tasklist');
if (change['new']) change.new_ = rcmail.gettext('status-'+String(change['new']).toLowerCase(), 'tasklist');
}
// format attachments struct
if (prop == 'attachments') {
if (change['old']) task_show_attachments([change['old']], row.children('.task-text-old'), rec, false);
else row.children('.task-text-old').text('--');
if (change['new']) task_show_attachments([$.extend({}, change['old'] || {}, change['new'])], row.children('.task-text-new'), rec, false);
else row.children('.task-text-new').text('--');
// remove click handler in diff view
$('.attachmentslist li a', row).unbind('click').removeAttr('href');
}
else if (change.diff_) {
row.children('.task-text-diff').html(change.diff_);
row.children('.task-text-old, .task-text-new').hide();
}
else {
if (!html) {
// escape HTML characters
change.old_ = Q(change.old_ || change['old'] || '--')
change.new_ = Q(change.new_ || change['new'] || '--')
}
row.children('.task-text-old').html(change.old_ || change['old'] || '--').show();
row.children('.task-text-new').html(change.new_ || change['new'] || '--').show();
}
// display index number
if (typeof change.index != 'undefined') {
row.find('.index').html('(' + change.index + ')');
}
row.show().data('set', true);
});
// open jquery UI dialog
$dialog.dialog({
modal: false,
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('objectdiff','tasklist').replace('$rev1', data.rev1).replace('$rev2', data.rev2) + ' - ' + rec.title,
open: function() {
$dialog.attr('aria-hidden', 'false');
},
close: function() {
$dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
},
buttons: [
{
text: rcmail.gettext('close'),
click: function() { $dialog.dialog('close'); },
autofocus: true
}
],
minWidth: 320,
width: 450
}).show();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 400);
}
// close the event history dialog
function close_history_dialog()
{
$('#taskhistory, #taskdiff').each(function(i, elem) {
var $dialog = $(elem);
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
});
};
/**
* Opens the dialog to edit a task
*/
function task_edit_dialog(id, action, presets)
{
$('#taskshow:ui-dialog').dialog('close');
var selected_list, rec = listdata[id] || presets,
$dialog = $('<div>'),
editform = $('#taskedit'),
list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] :
(me.selected_list ? me.tasklists[me.selected_list] : { editable: action == 'new', rights: action == 'new' ? 'rwitd' : 'r' });
if (rcmail.busy || !has_permission(list, 'i') || (action == 'edit' && (!rec || rec.readonly)))
return false;
me.selected_task = $.extend({ valarms:[] }, rec); // clone task object
rec = me.selected_task;
// assign temporary id
if (!me.selected_task.id)
me.selected_task.id = -(++idcount);
// reset dialog first
$('#taskeditform').get(0).reset();
// fill form data
var title = $('#taskedit-title').val(rec.title || '');
var description = $('#taskedit-description').val(rec.description || '');
var recdate = $('#taskedit-date').val(rec.date || '');
var rectime = $('#taskedit-time').val(rec.time || '');
var recstartdate = $('#taskedit-startdate').val(rec.startdate || '');
var recstarttime = $('#taskedit-starttime').val(rec.starttime || '');
var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100);
completeness_slider.slider('value', complete.val());
var taskstatus = $('#taskedit-status').val(rec.status || '');
var tasklist = $('#taskedit-tasklist').prop('disabled', rec.parent_id ? true : false);
var notify = $('#edit-attendees-donotify').get(0);
var invite = $('#edit-attendees-invite').get(0);
var comment = $('#edit-attendees-comment');
invite.checked = settings.itip_notify & 1 > 0;
notify.checked = has_attendees(rec) && invite.checked;
// set tasklist selection according to permissions
tasklist.find('option').each(function(i, opt) {
var l = me.tasklists[opt.value] || {},
writable = l.editable || (action == 'new' && has_permission(l, 'i'));
$(opt).prop('disabled', !writable);
if (!selected_list && writable)
selected_list = opt.value;
});
tasklist.val(rec.list || me.selected_list || selected_list);
// tag-edit line
var tagline = $(rcmail.gui_objects.edittagline).empty();
$.each(typeof rec.tags == 'object' && rec.tags.length ? rec.tags : [''], function(i,val){
$('<input>')
.attr('name', 'tags[]')
.attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
$('input.tag', rcmail.gui_objects.edittagline).tagedit({
animSpeed: 100,
allowEdit: false,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: tags, minLength: 0, noCheck: true, appendTo:'#taskedit' },
texts: { removeLinkTitle: rcmail.gettext('removetag', 'tasklist') }
});
// set alarm(s)
me.set_alarms_edit('#taskedit-alarms', action != 'new' && rec.valarms ? rec.valarms : []);
if ($.isArray(rec.links) && rec.links.length) {
render_message_links(rec.links, $('#taskedit-links .task-text'), true, 'tasklist');
$('#taskedit-links').show();
}
else {
$('#taskedit-links').hide();
}
// set recurrence
me.set_recurrence_edit(rec);
// init attendees tab
var organizer = !rec.attendees || is_organizer(rec),
allow_invitations = organizer || (rec.owner && rec.owner == 'anonymous') || settings.invite_shared;
task_attendees = [];
attendees_list = $('#edit-attendees-table > tbody').html('');
$('#edit-attendees-notify')[(allow_invitations && has_attendees(rec) && (settings.itip_notify & 2) ? 'show' : 'hide')]();
$('#edit-localchanges-warning')[(has_attendees(rec) && !(allow_invitations || (rec.owner && is_organizer(rec, rec.owner))) ? 'show' : 'hide')]();
// attendees (aka assignees)
if (list.attendees) {
var j, data, reply_selected = 0;
if (rec.attendees) {
for (j=0; j < rec.attendees.length; j++) {
data = rec.attendees[j];
add_attendee(data, !allow_invitations);
if (allow_invitations && !data.noreply) {
reply_selected++;
}
}
}
// make sure comment box is visible if at least one attendee has reply enabled
// or global "send invitations" checkbox is checked
$('#taskeditform .attendees-commentbox')[(reply_selected || invite.checked ? 'show' : 'hide')]();
// select the correct organizer identity
var identity_id = 0;
$.each(settings.identities, function(i,v) {
if (!rec.organizer || v == rec.organizer.email) {
identity_id = i;
return false;
}
});
$('#edit-tab-attendees').show();
$('#edit-attendees-form')[(allow_invitations?'show':'hide')]();
$('#edit-identities-list').val(identity_id);
$('#taskedit-organizer')[(organizer ? 'show' : 'hide')]();
}
else {
$('#edit-tab-attendees').hide();
}
// attachments
rcmail.enable_command('remove-attachment', list.editable);
me.selected_task.deleted_attachments = [];
// we're sharing some code for uploads handling with app.js
rcmail.env.attachments = [];
rcmail.env.compose_id = me.selected_task.id; // for rcmail.async_upload_form()
if ($.isArray(rec.attachments)) {
task_show_attachments(rec.attachments, $('#taskedit-attachments'), rec, true);
}
else {
$('#taskedit-attachments > ul').empty();
}
// show/hide tabs according to calendar's feature support
$('#taskedit-tab-attachments')[(list.attachments||rec.attachments?'show':'hide')]();
// activate the first tab
$('#taskedit').tabs('option', 'active', 0);
// define dialog buttons
var buttons = [];
buttons.push({
text: rcmail.gettext('save', 'tasklist'),
'class': 'mainaction',
click: function() {
var data = me.selected_task;
data._status_before = me.selected_task.status + '';
// copy form field contents into task object to save
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, status:taskstatus }, function(key,input){
data[key] = input.val();
});
data.list = tasklist.find('option:selected').val();
data.tags = [];
data.attachments = [];
data.attendees = task_attendees;
data.valarms = me.serialize_alarms('#taskedit-alarms');
data.recurrence = me.serialize_recurrence(rectime.val());
// do some basic input validation
if (!data.title || !data.title.length) {
title.focus();
return false;
}
else if (data.startdate && data.date) {
var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, data.startdate, datepicker_settings);
var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, data.date, datepicker_settings);
if (startdate > duedate) {
alert(rcmail.gettext('invalidstartduedates', 'tasklist'));
return false;
}
else if ((data.time == '') != (data.starttime == '')) {
alert(rcmail.gettext('invalidstartduetimes', 'tasklist'));
return false;
}
}
else if (data.recurrence && !data.startdate && !data.date) {
alert(rcmail.gettext('recurrencerequiresdate', 'tasklist'));
return false;
}
// collect tags
$('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem) {
if (elem.value)
data.tags.push(elem.value);
});
// including the "pending" one in the text box
var newtag = $('#tagedit-input').val();
if (newtag != '') {
data.tags.push(newtag);
}
// uploaded attachments list
for (var i in rcmail.env.attachments) {
if (i.match(/^rcmfile(.+)/))
data.attachments.push(RegExp.$1);
}
// task assigned to a new list
if (data.list && listdata[id] && data.list != listdata[id].list) {
data._fromlist = list.id;
}
data.complete = complete.val() / 100;
if (isNaN(data.complete))
data.complete = null;
else if (data.complete == 1.0 && rec.status === '')
data.status = 'COMPLETED';
if (!data.list && list.id)
data.list = list.id;
if (!data.tags.length)
data.tags = '';
if (organizer) {
data._identity = $('#edit-identities-list option:selected').val();
delete data.organizer;
}
// per-attendee notification suppression
var need_invitation = false;
if (allow_invitations) {
$.each(data.attendees, function (i, v) {
if (v.role != 'ORGANIZER') {
if ($('input.edit-attendee-reply[value="' + v.email + '"]').prop('checked')) {
need_invitation = true;
delete data.attendees[i]['noreply'];
}
else if (settings.itip_notify > 0) {
data.attendees[i].noreply = 1;
}
}
});
}
// tell server to send notifications
if ((has_attendees(data) || (rec.id && has_attendees(rec))) && allow_invitations && (notify.checked || invite.checked || need_invitation)) {
data._notify = settings.itip_notify;
data._comment = comment.val();
}
else if (data._notify) {
delete data._notify;
}
if (save_task(data, action))
$dialog.dialog('close');
} // end click:
});
if (action != 'new') {
buttons.push({
text: rcmail.gettext('delete', 'tasklist'),
'class': 'delete',
click: function() {
if (delete_task(rec.id))
$dialog.dialog('close');
}
});
}
buttons.push({
text: rcmail.gettext('cancel', 'tasklist'),
click: function() {
$dialog.dialog('close');
}
});
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
closeOnEscape: false,
title: rcmail.gettext((action == 'edit' ? 'edittask' : 'newtask'), 'tasklist'),
close: function() {
rcmail.ksearch_blur();
editform.hide().appendTo(document.body);
$dialog.dialog('destroy').remove();
},
buttons: buttons,
minHeight: 460,
minWidth: 500,
width: 580
}).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE
title.select();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
}
/**
* Open a task attachment either in a browser window for inline view or download it
*/
function load_attachment(rec, att)
{
// can't open temp attachments
if (!rec.id || rec.id < 0)
return false;
var query = { _id: att.id, _t: rec.recurrence_id||rec.id, _list:rec.list, _frame: 1 };
if (rec.rev)
query._rev = rec.rev;
// open attachment in frame if it's of a supported mimetype
// similar as in app.js and calendar_ui.js
if (att.id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
if (rcmail.open_window(rcmail.url('get-attachment', query), true, true)) {
return;
}
}
query._frame = null;
query._download = 1;
rcmail.goto_url('get-attachment', query, false);
};
/**
* Build task attachments list
*/
function task_show_attachments(list, container, rec, edit)
{
var i, id, len, content, li, elem,
ul = $('<ul>').addClass('attachmentslist');
for (i=0, len=list.length; i<len; i++) {
elem = list[i];
li = $('<li>').addClass(elem.classname);
if (edit) {
rcmail.env.attachments[elem.id] = elem;
// delete icon
content = $('<a>')
.attr('href', '#delete')
.attr('title', rcmail.gettext('delete'))
.addClass('delete')
.click({ id:elem.id }, function(e) {
remove_attachment(this, e.data.id);
return false;
});
if (!rcmail.env.deleteicon) {
content.html(rcmail.gettext('delete'));
}
else {
$('<img>').attr('src', rcmail.env.deleteicon).attr('alt', rcmail.gettext('delete')).appendTo(content);
}
li.append(content);
}
// name/link
$('<a>')
.attr('href', '#load')
.addClass('file')
.html(elem.name).click({ task:rec, att:elem }, function(e) {
load_attachment(e.data.task, e.data.att);
return false;
}).appendTo(li);
ul.append(li);
}
if (edit && rcmail.gui_objects.attachmentlist) {
ul.id = rcmail.gui_objects.attachmentlist.id;
rcmail.gui_objects.attachmentlist = ul.get(0);
}
container.empty().append(ul);
};
/**
*
*/
function remove_attachment(elem, id)
{
$(elem.parentNode).hide();
me.selected_task.deleted_attachments.push(id);
delete rcmail.env.attachments[id];
}
/**
*
*/
function remove_link(elem)
{
var $elem = $(elem), uri = $elem.attr('data-uri');
// remove the link item matching the given uri
me.selected_task.links = $.grep(me.selected_task.links, function(link) { return link.uri != uri; });
// remove UI list item
$elem.hide().closest('li').addClass('deleted');
}
/**
*
*/
function add_childtask(id)
{
if (rcmail.busy)
return false;
var rec = listdata[id];
task_edit_dialog(null, 'new', { parent_id:id, list:rec.list });
}
/**
* Delete the given task
*/
function delete_task(id)
{
var rec = listdata[id];
if (!rec || rec.readonly || rcmail.busy)
return false;
var html, buttons = [], $dialog = $('<div>');
// Subfunction to submit the delete command after confirm
var _delete_task = function(id, mode) {
var rec = listdata[id],
li = $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).hide(),
decline = $dialog.find('input.confirm-attendees-decline:checked').length,
notify = $dialog.find('input.confirm-attendees-notify:checked').length;
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('task', { action:'delete', t:{ id:rec.id, list:rec.list, _decline:decline, _notify:notify }, mode:mode, filter:filtermask });
// move childs to parent/root
if (mode != 1 && rec.children !== undefined) {
var parent_node = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > .childtasks', rcmail.gui_objects.resultlist) : null;
if (!parent_node || !parent_node.length)
parent_node = rcmail.gui_objects.resultlist;
$.each(rec.children, function(i,cid) {
var child = listdata[cid];
child.parent_id = rec.parent_id;
resort_task(child, $('li[rel="'+cid+'"]').appendTo(parent_node), true);
});
}
li.remove();
delete listdata[id];
}
if (rec.children && rec.children.length) {
html = rcmail.gettext('deleteparenttasktconfirm','tasklist');
buttons.push({
text: rcmail.gettext('deletethisonly','tasklist'),
click: function() {
_delete_task(id, 0);
$(this).dialog('close');
}
});
buttons.push({
text: rcmail.gettext('deletewithchilds','tasklist'),
click: function() {
_delete_task(id, 1);
$(this).dialog('close');
}
});
}
else {
html = rcmail.gettext('deletetasktconfirm','tasklist');
buttons.push({
text: rcmail.gettext('delete','tasklist'),
'class': 'delete',
click: function() {
_delete_task(id, 0);
$(this).dialog('close');
}
});
}
if (is_attendee(rec)) {
html += '<div class="task-dialog-message">' +
'<label><input class="confirm-attendees-decline" type="checkbox" checked="checked" value="1" name="_decline" />&nbsp;' +
rcmail.gettext('itipdeclinetask', 'tasklist') +
'</label></div>';
}
else if (has_attendees(rec) && is_organizer(rec)) {
html += '<div class="task-dialog-message">' +
'<label><input class="confirm-attendees-notify" type="checkbox" checked="checked" value="1" name="_notify" />&nbsp;' +
rcmail.gettext('sendcancellation', 'tasklist') +
'</label></div>';
}
buttons.push({
text: rcmail.gettext('cancel', 'tasklist'),
click: function() {
$(this).dialog('close');
}
});
$dialog.html(html);
$dialog.dialog({
modal: true,
width: 520,
dialogClass: 'warning no-close',
title: rcmail.gettext('deletetask', 'tasklist'),
buttons: buttons,
close: function(){
$dialog.dialog('destroy').hide();
}
}).addClass('tasklist-confirm').show();
return true;
}
/**
* Check if the given task matches the current filtermask and tag selection
*/
function match_filter(rec, cache, recursive)
{
if (!rec)
return false;
// return cached result
if (typeof cache[rec.id] != 'undefined' && recursive != 2) {
return cache[rec.id];
}
var match = !filtermask || (filtermask & rec.mask) == filtermask;
// in focusview mode, only tasks from the selected list are allowed
if (focusview)
match = $.inArray(rec.list, focusview_lists) >= 0 && match;
if (match && tagsfilter.length) {
match = rec.tags && rec.tags.length;
var alltags = get_inherited_tags(rec).concat(rec.tags || []);
for (var i=0; match && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], alltags) < 0)
match = false;
}
}
// check if a child task matches the tags
if (!match && (recursive||0) < 2 && rec.children && rec.children.length) {
for (var j=0; !match && j < rec.children.length; j++) {
match = match_filter(listdata[rec.children[j]], cache, 1);
}
}
// walk up the task tree and check if a parent task matches
var parent_id;
if (!match && !recursive && (parent_id = rec.parent_id)) {
while (!match && parent_id && listdata[parent_id]) {
match = match_filter(listdata[parent_id], cache, 2);
parent_id = listdata[parent_id].parent_id;
}
}
if (recursive != 1) {
cache[rec.id] = match;
}
return match;
}
/**
*
*/
function get_inherited_tags(rec)
{
var parent_id, itags = [];
if ((parent_id = rec.parent_id)) {
while (parent_id && listdata[parent_id]) {
itags = itags.concat(listdata[parent_id].tags || []);
parent_id = listdata[parent_id].parent_id;
}
}
return $.unqiqueStrings(itags);
}
/**
* Change tasks list sorting
*/
function list_set_sort(col)
{
if (settings.sort_col != col) {
settings.sort_col = col;
$('#taskviewsortmenu .sortcol').attr('aria-checked', 'false').removeClass('selected')
.filter('.by-' + col).attr('aria-checked', 'true').addClass('selected');
// re-sort list index and re-render list
listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
render_tasklist();
rcmail.enable_command('list-order', settings.sort_col != 'auto');
$('#taskviewsortmenu .sortorder').removeClass('selected').filter('[aria-checked=true]').addClass('selected');
rcmail.save_pref({ name: 'tasklist_sort_col', value: (col == 'auto' ? '' : col) });
}
}
/**
* Change tasks list sort order
*/
function list_set_order(order)
{
if (settings.sort_order != order) {
settings.sort_order = order;
$('#taskviewsortmenu .sortorder').attr('aria-checked', 'false').removeClass('selected')
.filter('.' + order).attr('aria-checked', 'true').addClass('selected');
// re-sort list index and re-render list
listindex.sort(function(a, b) { return task_cmp(listdata[a], listdata[b]); });
render_tasklist();
rcmail.save_pref({ name: 'tasklist_sort_order', value: order });
}
}
/**
*
*/
function list_edit_dialog(id)
{
var list = me.tasklists[id],
$dialog = $(rcmail.gui_containers.tasklistform);
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (!list)
list = { name:'', editable:true, rights:'riwta', showalarms:true };
var editform, name, alarms;
$dialog.html(rcmail.get_label('loading'));
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('tasklist'),
data: { action:(list.id ? 'form-edit' : 'form-new'), l:{ id:list.id } },
success: function(data) {
$dialog.html(data);
rcmail.triggerEvent('tasklist_editform_load', list);
// resize and reposition dialog window
editform = $('#tasklisteditform');
me.dialog_resize(rcmail.gui_containers.tasklistform, editform.height(), editform.width());
name = $('#taskedit-tasklistame').prop('disabled', !has_permission(list, 'a')||list.norename).val(list.editname || list.name);
alarms = $('#taskedit-showalarms').prop('checked', list.showalarms).get(0);
name.select();
// suppress form submission with <Enter>
editform.on('submit', function(e) {
e.preventDefault();
return false;
});
}
});
// dialog buttons
var buttons = [];
buttons.push({
text: rcmail.gettext('save','tasklist'),
'class': 'mainaction',
click: function() {
// do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidlistproperties', 'tasklist'));
name.select();
return;
}
// post data to server
var data = editform.serializeJSON();
if (list.id)
data.id = list.id;
if (alarms)
data.showalarms = alarms.checked ? 1 : 0;
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasklist', { action:(list.id ? 'edit' : 'new'), l:data });
$dialog.dialog('close');
} // end click:
});
buttons.push({
text: rcmail.gettext('cancel','tasklist'),
click: function() {
$dialog.dialog('close');
}
});
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'tasklist'),
close: function() {
$dialog.dialog('destroy').hide();
},
buttons: buttons,
minWidth: 400,
width: 420
}).show();
}
/**
*
*/
function list_delete(id)
{
var list = me.tasklists[id];
if (list && !list.norename && confirm(rcmail.gettext(list.children ? 'deletelistconfirmrecursive' : 'deletelistconfirm', 'tasklist'))) {
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasklist', { action:'delete', l:{ id:list.id } });
return true;
}
return false;
}
/**
*
*/
function list_remove(id)
{
var list = me.tasklists[id];
if (list && list.removable) {
destroy_list(list);
rcmail.http_post('tasklist', { action:'subscribe', l:{ id:list.id, active:0, permanent:0, recursive:1 } });
}
}
/**
* Callback from server to finally remove the given list
*/
function destroy_list(prop)
{
var li, delete_ids = [],
list = me.tasklists[prop.id];
// find sub-lists
if (list && list.children) {
for (var child_id in me.tasklists) {
if (String(child_id).indexOf(prop.id) == 0)
delete_ids.push(child_id);
}
}
else {
delete_ids.push(prop.id);
}
// delete all subfolders in the list
for (var i=0; i < delete_ids.length; i++) {
id = delete_ids[i];
list = me.tasklists[id];
tasklists_widget.remove(id);
if (list) {
list.active = false;
// delete me.tasklists[prop.id];
unlock_saving();
remove_tasks(list.id);
}
$("#taskedit-tasklist option[value='"+id+"']").remove();
}
}
/**
*
*/
function insert_list(prop)
{
if (prop._reload) {
rcmail.redirect(rcmail.url(''));
return;
}
tasklists_widget.insert({
id: prop.id,
classes: [ prop.group || '' ],
virtual: prop.virtual,
html: prop.html
}, prop.parent || null, prop.group);
// flag as tasklist for drag & drop
$(tasklists_widget.get_item(prop.id)).data('type', 'tasklist');
delete prop.html;
me.tasklists[prop.id] = prop;
// append to list selector in task edit dialog, too (#2985)
$('<option>').attr('value', prop.id).html(Q(prop.name)).appendTo('#taskedit-tasklist');
}
/**
*
*/
function update_list(prop)
{
var id = prop.oldid || prop.id,
li = tasklists_widget.get_item(id);
if (prop._reload) {
rcmail.redirect(rcmail.url(''));
return;
}
if (me.tasklists[id] && li) {
delete me.tasklists[id];
me.tasklists[prop.id] = prop;
$(li).find('input').first().val(prop.id);
$(li).find('.listname').first().html(Q(prop.name));
tasklists_widget.update(id, { id:prop.id, html:li.children().first() });
}
}
/**
*
*/
function list_showurl(id)
{
var list = me.tasklists[id];
if (list && list.caldavurl) {
$('div.showurldialog:ui-dialog').dialog('close');
var $dialog = $('<div>').addClass('showurldialog').append('<p>'+rcmail.gettext('caldavurldescription', 'tasklist')+'</p>'),
textbox = $('<textarea>').addClass('urlbox').css('width', '100%').attr('rows', 2).appendTo($dialog);
$dialog.dialog({
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('showcaldavurl', 'tasklist'),
close: function() {
$dialog.dialog("destroy").remove();
},
width: 520
}).show();
textbox.val(list.caldavurl).select();
}
}
/**
* Execute search
*/
function quicksearch()
{
var q;
if (rcmail.gui_objects.qsearchbox && (q = rcmail.gui_objects.qsearchbox.value)) {
var id = 'search-'+q;
var resources = [];
for (var rid in me.tasklists) {
if (me.tasklists[rid].active) {
resources.push(rid);
}
}
id += '@'+resources.join(',');
// ignore if query didn't change
if (search_request == id)
return;
search_request = id;
search_query = q;
list_tasks();
}
else // empty search input equals reset
this.reset_search();
}
/**
* Reset search and get back to normal listing
*/
function reset_search()
{
$(rcmail.gui_objects.qsearchbox).val('');
if (search_request) {
search_request = search_query = null;
list_tasks();
}
}
// method to show the print dialog.
function print_tasks()
{
var param = {}, active = active_lists();
if (active.length) {
param = {filter: filtermask, lists: active.join(','), q: search_query};
rcmail.open_window(rcmail.url('print', param), true, true);
}
};
/**** Utility functions ****/
// same as str.split(delimiter) but it ignores delimiters within quoted strings
var explode_quoted_string = function(str, delimiter)
{
var result = [],
strlen = str.length,
q, p, i, chr, last;
for (q = p = i = 0; i < strlen; i++) {
chr = str.charAt(i);
if (chr == '"' && last != '\\') {
q = !q;
}
else if (!q && chr == delimiter) {
result.push(str.substring(p, i));
p = i + 1;
}
last = chr;
}
result.push(str.substr(p));
return result;
};
/**
* Clear any text selection
* (text is probably selected when double-clicking somewhere)
*/
function clearSelection()
{
if (document.selection && document.selection.empty) {
document.selection.empty() ;
}
else if (window.getSelection) {
var sel = window.getSelection();
if (sel && sel.removeAllRanges)
sel.removeAllRanges();
}
}
/**
* Check whether the event target is a descentand of the given element
*/
function target_overlaps(target, elem)
{
while (target.parentNode) {
if (target.parentNode == elem)
return true;
target = target.parentNode;
}
return false;
}
/**
*
*/
function active_lists()
{
var active = [];
for (var id in me.tasklists) {
if (me.tasklists[id].active)
active.push(id);
}
return active;
}
// resize and reposition (center) the dialog window
this.dialog_resize = function(id, height, width)
{
var win = $(window), w = win.width(), h = win.height();
- $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) })
- .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
+ $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) });
};
/**
* Enable/disable focusview mode for the given list
*/
function set_focusview(id, shift)
{
var in_focus = $.inArray(id, focusview_lists) >= 0,
li = $(tasklists_widget.get_item(id)).find('.tasklist').first();
// remove list from focusview
if (in_focus && shift && id !== null) {
focusview_lists = $.grep(focusview_lists, function(list_id) { return list_id != id; });
}
else {
if (!shift || id === null) {
focusview_lists = [];
// uncheck all active focusview icons
tasklists_widget.container.find('div.focusview')
.removeClass('focusview')
.find('a.quickview').attr('aria-checked', 'false');
}
if (!in_focus && id !== null) {
focusview_lists.push(id)
}
}
focusview = focusview_lists.length > 0;
// activate list if necessary
if (focusview && !me.tasklists[id].active) {
li.find('input[type=checkbox]').get(0).checked = true;
me.tasklists[id].active = true;
fetch_counts();
}
// update list
list_tasks(null);
if (focusview) {
li[in_focus ? 'removeClass' : 'addClass']('focusview')
.find('a.quickview').attr('aria-checked', in_focus ? 'false' : 'true');
$('body').addClass('quickview-active');
}
else {
$('body').removeClass('quickview-active');
}
}
// init dialog by default
init_taskedit();
}
// extend jQuery
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
jQuery.fn.sortElements = (function(){
var sort = [].sort;
return function(comparator, getSortable) {
getSortable = getSortable || function(){ return this };
var last = null;
return sort.call(this, comparator).each(function(i){
// at this point the array is sorted, so we can just detach each one from wherever it is, and add it after the last
var node = $(getSortable.call(this));
var parent = node.parent();
if (last) last.after(node);
else parent.prepend(node);
last = node;
});
};
})();
// equivalent to $.unique() but working on arrays of strings
jQuery.unqiqueStrings = (function() {
return function(arr) {
var hash = {}, out = [];
for (var i = 0; i < arr.length; i++) {
hash[arr[i]] = 0;
}
for (var val in hash) {
out.push(val);
}
return out;
};
})();
/* tasklist plugin UI initialization */
var rctasks;
window.rcmail && rcmail.addEventListener('init', function(evt) {
rctasks = new rcube_tasklist_ui($.extend(rcmail.env.tasklist_settings, rcmail.env.libcal_settings));
// register button commands
rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
rcmail.register_command('print', function(){ rctasks.print_tasks(); }, true);
rcmail.register_command('list-create', function(){ rctasks.list_edit_dialog(null); }, true);
rcmail.register_command('list-edit', function(){ rctasks.list_edit_dialog(rctasks.selected_list); }, false);
rcmail.register_command('list-delete', function(){ rctasks.list_delete(rctasks.selected_list); }, false);
rcmail.register_command('list-remove', function(){ rctasks.list_remove(rctasks.selected_list); }, false);
rcmail.register_command('list-showurl', function(){ rctasks.list_showurl(rctasks.selected_list); }, false);
rcmail.register_command('export', function(){ rctasks.export_tasks(); }, true);
rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true);
rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true);
rcmail.register_command('expand-all', function(){ rctasks.expand_collapse(true); }, true);
rcmail.register_command('collapse-all', function(){ rctasks.expand_collapse(false); }, true);
rctasks.init();
});

File Metadata

Mime Type
text/x-diff
Expires
Mon, Aug 17, 9:58 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1261660
Default Alt Text
(474 KB)

Event Timeline