Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117755587
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
208 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 83f69e02..6bb5d78d 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -1,2761 +1,2758 @@
/**
* Client UI Javascript for the Calendar plugin
*
* @version @package_version@
* @author Lazlo Westerhof <hello@lazlo.me>
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2012, 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/>.
*/
// 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;
/*** 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 attendees_list;
var freebusy_ui = { workinhoursonly:false, needsupdate:false };
var freebusy_data = {};
var current_view = null;
var exec_deferred = bw.ie6 ? 5 : 1;
var sensitivitylabels = { 0:rcmail.gettext('public','calendar'), 1:rcmail.gettext('private','calendar'), 2: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
};
/*** 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 init_alarms_edit = this.init_alarms_edit;
/*** 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, char, last;
for (q = p = i = 0; i < strlen; i++) {
char = str.charAt(i);
if (char == '"' && last != '\\') {
q = !q;
}
else if (!q && char == delimiter) {
result.push(str.substring(p, i));
p = i + 1;
}
last = char;
}
result.push(str.substr(p));
return result;
};
// 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);
};
// turn the given date into an ISO 8601 date string understandable by PHPs strtotime()
var date2servertime = function(date)
{
return date.getFullYear()+'-'+zeropad(date.getMonth()+1)+'-'+zeropad(date.getDate())
+ 'T'+zeropad(date.getHours())+':'+zeropad(date.getMinutes())+':'+zeropad(date.getSeconds());
}
var date2timestring = function(date, dateonly)
{
return date2servertime(date).replace(/[^0-9]/g, '').substr(0, (dateonly ? 8 : 14));
}
var zeropad = function(num)
{
return (num < 10 ? '0' : '') + num;
}
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;
};
var load_attachment = function(event, att)
{
var qstring = '_id='+urlencode(att.id)+'&_event='+urlencode(event.recurrence_id||event.id)+'&_cal='+urlencode(event.calendar);
// open attachment in frame if it's of a supported mimetype
if (id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
- rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubeeventattachment');
- if (rcmail.attachment_win) {
- window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
+ if (rcmail.open_window(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', true, true)) {
return;
}
}
rcmail.goto_url('get-attachment', qstring+'&_download=1', 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 = document.createElement('A');
content.href = '#delete';
content.title = rcmail.gettext('delete');
content.className = 'delete';
$(content).click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
if (!rcmail.env.deleteicon)
content.innerHTML = rcmail.gettext('delete');
else {
img = document.createElement('IMG');
img.src = rcmail.env.deleteicon;
img.alt = rcmail.gettext('delete');
content.appendChild(img);
}
li.appendChild(content);
}
// name/link
content = document.createElement('A');
content.innerHTML = elem.name;
content.className = 'file';
content.href = '#load';
$(content).click({event: event, att: elem}, function(e) {
load_attachment(e.data.event, e.data.att); return false; });
li.appendChild(content);
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)
{
var $dialog = $("#eventshow").removeClass().addClass('uidialog');
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
me.selected_event = event;
// 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.alarms && 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)).removeClass().addClass('event-text').addClass('cal-'+calendar.id);
if (event.categories)
$('#event-category').show().children('.event-text').html(Q(event.categories)).removeClass().addClass('event-text cat-'+String(event.categories).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.sensitivity != 0) {
var sensitivityclasses = { 0:'public', 1:'private', 2:'confidential' };
$('#event-sensitivity').show().children('.event-text').html(Q(sensitivitylabels[event.sensitivity]));
$dialog.addClass('sensitivity-'+sensitivityclasses[event.sensitivity]);
}
// 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?
}
// list event attendees
if (calendar.attendees && event.attendees) {
var data, dispname, organizer = false, rsvp = false, html = '';
for (var j=0; j < event.attendees.length; j++) {
data = event.attendees[j];
dispname = Q(data.name || data.email);
if (data.email) {
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink">' + dispname + '</a>';
if (data.role == 'ORGANIZER')
organizer = true;
else if ((data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE') && settings.identity.emails.indexOf(';'+data.email) >= 0)
rsvp = data.status.toLowerCase();
}
html += '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '">' + dispname + '</span> ';
// stop listing attendees
if (j == 7 && event.attendees.length >= 7) {
html += ' <em>' + rcmail.gettext('andnmore', 'calendar').replace('$nr', event.attendees.length - j - 1) + '</em>';
break;
}
}
if (html && (event.attendees.length > 1 || !organizer)) {
$('#event-attendees').show()
.children('.event-text')
.html(html)
.find('a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
}
$('#event-rsvp')[(rsvp&&!organizer?'show':'hide')]();
$('#event-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+rsvp+']').prop('disabled', true);
}
var buttons = {};
if (calendar.editable && event.editable !== false) {
buttons[rcmail.gettext('edit', 'calendar')] = function() {
event_edit_dialog('edit', event);
};
buttons[rcmail.gettext('remove', 'calendar')] = function() {
me.delete_event(event);
$dialog.dialog('close');
};
}
else {
buttons[rcmail.gettext('close', 'calendar')] = 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: Q(me.event_date_text(event)),
open: function() {
$dialog.parent().find('.ui-button').first().focus();
},
close: function() {
$dialog.dialog('destroy').hide();
},
buttons: buttons,
minWidth: 320,
width: 420
}).show();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 420);
/*
// add link for "more options" drop-down
$('<a>')
.attr('href', '#')
.html('More Options')
.addClass('dropdown-link')
.click(function(){ return false; })
.insertBefore($dialog.parent().find('.ui-dialog-buttonset').children().first());
*/
};
// bring up the event dialog (jquery-ui popup)
var event_edit_dialog = function(action, event)
{
// close show dialog first
$("#eventshow:ui-dialog").dialog('close');
var $dialog = $('<div>');
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' };
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();
// 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').html(event.description || '');
var vurl = $('#edit-url').val(event.vurl || '');
var categories = $('#edit-categories').val(event.categories);
var calendars = $('#edit-calendar').val(event.calendar);
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);
notify.checked = has_attendees(event), invite.checked = true;
if (event.allDay) {
starttime.val("12:00").hide();
endtime.val("13:00").hide();
allday.checked = true;
}
else {
allday.checked = false;
}
// set alarm(s)
// TODO: support multiple alarm entries
if (event.alarms || action != 'new') {
if (typeof event.alarms == 'string')
event.alarms = event.alarms.split(';');
var valarms = event.alarms || [''];
for (var alarm, i=0; i < valarms.length; i++) {
alarm = String(valarms[i]).split(':');
if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY';
$('#eventedit select.edit-alarm-type').val(alarm[1]);
if (alarm[0].match(/@(\d+)/)) {
var ondate = fromunixtime(parseInt(RegExp.$1));
$('#eventedit select.edit-alarm-offset').val('@');
$('#eventedit input.edit-alarm-date').val($.fullCalendar.formatDate(ondate, settings['date_format']));
$('#eventedit input.edit-alarm-time').val($.fullCalendar.formatDate(ondate, settings['time_format']));
}
else if (alarm[0].match(/([-+])(\d+)([MHD])/)) {
$('#eventedit input.edit-alarm-value').val(RegExp.$2);
$('#eventedit select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
}
}
}
// set correct visibility by triggering onchange handlers
$('#eventedit select.edit-alarm-type, #eventedit select.edit-alarm-offset').change();
// 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);
}
// set recurrence form
var recurrence, interval, rrtimes, rrenddate;
var load_recurrence_tab = function()
{
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
interval = $('#eventedit select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
$('#eventedit input.edit-recurrence-until:checked').prop('checked', false);
var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
var rrepeat_id = '#edit-recurrence-repeat-forever';
if (event.recurrence && event.recurrence.COUNT) rrepeat_id = '#edit-recurrence-repeat-count';
else if (event.recurrence && event.recurrence.UNTIL) rrepeat_id = '#edit-recurrence-repeat-until';
$(rrepeat_id).prop('checked', true);
if (event.recurrence && event.recurrence.BYDAY && event.recurrence.FREQ == 'WEEKLY') {
var wdays = event.recurrence.BYDAY.split(',');
$('input.edit-recurrence-weekly-byday').val(wdays);
}
if (event.recurrence && event.recurrence.BYMONTHDAY) {
$('input.edit-recurrence-monthly-bymonthday').val(String(event.recurrence.BYMONTHDAY).split(','));
$('input.edit-recurrence-monthly-mode').val(['BYMONTHDAY']);
}
if (event.recurrence && event.recurrence.BYDAY && (event.recurrence.FREQ == 'MONTHLY' || event.recurrence.FREQ == 'YEARLY')) {
var byday, section = event.recurrence.FREQ.toLowerCase();
if ((byday = String(event.recurrence.BYDAY).match(/(-?[1-4])([A-Z]+)/))) {
$('#edit-recurrence-'+section+'-prefix').val(byday[1]);
$('#edit-recurrence-'+section+'-byday').val(byday[2]);
}
$('input.edit-recurrence-'+section+'-mode').val(['BYDAY']);
}
else if (event.start) {
$('#edit-recurrence-monthly-byday').val(weekdays[event.start.getDay()]);
}
if (event.recurrence && event.recurrence.BYMONTH) {
$('input.edit-recurrence-yearly-bymonth').val(String(event.recurrence.BYMONTH).split(','));
}
else if (event.start) {
$('input.edit-recurrence-yearly-bymonth').val([String(event.start.getMonth()+1)]);
}
};
// show warning if editing a recurring event
if (event.id && event.recurrence) {
var sel = event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all');
$('#edit-recurring-warning').show();
$('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true);
}
else
$('#edit-recurring-warning').hide();
// init attendees tab
var organizer = !event.attendees || is_organizer(event);
event_attendees = [];
attendees_list = $('#edit-attendees-table > tbody').html('');
$('#edit-attendees-notify')[(notify.checked && organizer ? 'show' : 'hide')]();
$('#edit-localchanges-warning')[(has_attendees(event) && !(organizer || (calendar.owner && is_organizer(event, calendar.owner))) ? 'show' : 'hide')]();
var load_attendees_tab = function()
{
if (event.attendees) {
for (var j=0; j < event.attendees.length; j++)
add_attendee(event.attendees[j], !organizer);
}
// select the correct organizer identity
var identity_id = 0;
$.each(settings.identities, function(i,v){
if (organizer && v == organizer.email) {
identity_id = i;
return false;
}
});
$('#edit-identities-list').val(identity_id);
$('#edit-attendees-form')[(organizer?'show':'hide')]();
$('#edit-attendee-schedule')[(calendar.freebusy?'show':'hide')]();
};
// attachments
var load_attachments_tab = function()
{
rcmail.enable_command('remove-attachment', !calendar.readonly);
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[rcmail.gettext('save', 'calendar')] = 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(),
recurrence: '',
alarms: '',
attendees: event_attendees,
deleted_attachments: rcmail.env.deleted_attachments,
attachments: []
};
// serialize alarm settings
// TODO: support multiple alarm entries
var alarm = $('#eventedit select.edit-alarm-type').val();
if (alarm) {
var val, offset = $('#eventedit select.edit-alarm-offset').val();
if (offset == '@')
data.alarms = '@' + date2unixtime(parse_datetime($('#eventedit input.edit-alarm-time').val(), $('#eventedit input.edit-alarm-date').val())) + ':' + alarm;
else if ((val = parseInt($('#eventedit input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
data.alarms = offset[0] + val + offset[1] + ':' + alarm;
}
// 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();
// don't submit attendees if only myself is added as organizer
if (data.attendees.length == 1 && data.attendees[0].role == 'ORGANIZER' && String(data.attendees[0].email).toLowerCase() == settings.identity.email)
data.attendees = [];
// tell server to send notifications
if ((data.attendees.length || (event.id && event.attendees.length)) && organizer && (notify.checked || invite.checked)) {
data._notify = 1;
}
// gather recurrence settings
var freq;
if ((freq = recurrence.val()) != '') {
data.recurrence = {
FREQ: freq,
INTERVAL: $('#edit-recurrence-interval-'+freq.toLowerCase()).val()
};
var until = $('input.edit-recurrence-until:checked').val();
if (until == 'count')
data.recurrence.COUNT = rrtimes.val();
else if (until == 'until')
data.recurrence.UNTIL = date2servertime(parse_datetime(endtime.val(), rrenddate.val()));
if (freq == 'WEEKLY') {
var byday = [];
$('input.edit-recurrence-weekly-byday:checked').each(function(){ byday.push(this.value); });
if (byday.length)
data.recurrence.BYDAY = byday.join(',');
}
else if (freq == 'MONTHLY') {
var mode = $('input.edit-recurrence-monthly-mode:checked').val(), bymonday = [];
if (mode == 'BYMONTHDAY') {
$('input.edit-recurrence-monthly-bymonthday:checked').each(function(){ bymonday.push(this.value); });
if (bymonday.length)
data.recurrence.BYMONTHDAY = bymonday.join(',');
}
else
data.recurrence.BYDAY = $('#edit-recurrence-monthly-prefix').val() + $('#edit-recurrence-monthly-byday').val();
}
else if (freq == 'YEARLY') {
var byday, bymonth = [];
$('input.edit-recurrence-yearly-bymonth:checked').each(function(){ bymonth.push(this.value); });
if (bymonth.length)
data.recurrence.BYMONTH = bymonth.join(',');
if ((byday = $('#edit-recurrence-yearly-byday').val()))
data.recurrence.BYDAY = $('#edit-recurrence-yearly-prefix').val() + byday;
}
}
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");
};
if (event.id) {
buttons[rcmail.gettext('remove', 'calendar')] = function() {
me.delete_event(event);
$dialog.dialog('close');
};
}
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
// show/hide tabs according to calendar's feature support
$('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
// activate the first tab
$('#eventtabs').tabs('select', 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'),
close: function() {
editform.hide().appendTo(document.body);
$dialog.dialog("destroy").remove();
rcmail.ksearch_blur();
rcmail.ksearch_destroy();
freebusy_data = {};
rcmail.env.comm_path = comm_path_before; // restore comm_path
},
buttons: buttons,
minWidth: 500,
width: 580
}).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), 530);
title.select();
// init other tabs asynchronously
window.setTimeout(load_recurrence_tab, exec_deferred);
if (calendar.attendees)
window.setTimeout(load_attendees_tab, exec_deferred);
if (calendar.attachments)
window.setTimeout(load_attachments_tab, exec_deferred);
};
// 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')) + '"> </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"> </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', 'CHAIR' ];
if ((attendee = freebusy_ui.attendees[domid]) && attendee.role != 'ORGANIZER') {
var req = attendee.role != 'OPT-PARTICIPANT';
var j = $.inArray(attendee.role, roles);
j = (j+1) % roles.length;
attendee.role = roles[j];
$(e.target).parent().removeClass().addClass('attendee '+String(attendee.role).toLowerCase());
// update total display if required-status changed
if (req != (roles[j] != 'OPT-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() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().focus();
},
close: function() {
if (bw.ie6)
$("#edit-attendees-table").css('visibility','visible');
$dialog.dialog("destroy").hide();
},
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"> </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) + '"> </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()));
}
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)
{
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({ email:email, name:name, role:'REQ-PARTICIPANT', status:'NEEDS-ACTION' });
success = true;
}
else {
alert(rcmail.gettext('noemailwarning'));
}
}
return success;
};
// add the given attendee to the list
var add_attendee = function(data, readonly)
{
// check for dupes...
var exists = false;
$.each(event_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">' + 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['CHAIR'] = rcmail.gettext('calendar.rolechair');
if (organizer && !readonly)
dispname = rcmail.env['identities-selector'];
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + '>';
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 html = '<td class="role">' + select + '</td>' +
'<td class="name">' + dispname + '</td>' +
'<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" /></td>' +
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '">' + Q(data.status) + '</span></td>' +
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
var tr = $('<tr>')
.addClass(String(data.role).toLowerCase())
.html(html)
.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(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
// 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);
};
// iterate over all attendees and update their free-busy status display
var update_freebusy_status = function(event)
{
var icons = attendees_list.find('img.availabilityicon');
for (var i=0; i < event_attendees.length; i++) {
if (icons.get(i) && event_attendees[i].email)
check_freebusy_status(icons.get(i), event_attendees[i].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).removeClass().addClass('availabilityicon unknown');
return;
}
icon = $(icon).removeClass().addClass('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){
icon.removeClass('loading').addClass(String(status).toLowerCase());
},
error: function(){
icon.removeClass('loading').addClass('unknown');
}
});
};
// 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) });
};
// when the user accepts or declines an event invitation
var event_rsvp = function(response)
{
if (me.selected_event && me.selected_event.attendees && response) {
// update attendee status
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();
}
event_show_dialog(me.selected_event);
// submit status change to server
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('event', { action:'rsvp', e:me.selected_event, status:response });
}
}
// post the given event data to server
var update_event = function(action, data)
{
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('calendar/event', { action:action, e:data });
// 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);
}
};
// 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) {
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)
{
if (!data) data = event;
var decline = false, notify = false, html = '', cal = me.calendars[event.calendar];
// event has attendees, ask whether to notify them
if (has_attendees(event)) {
if (is_organizer(event)) {
notify = true;
html += '<div class="message">' +
'<label><input class="confirm-attendees-donotify" type="checkbox" checked="checked" value="1" name="notify" /> ' +
rcmail.gettext((action == 'remove' ? 'sendcancellation' : 'sendnotifications'), 'calendar') +
'</label></div>';
}
else if (action == 'remove' && is_attendee(event)) {
decline = true;
html += '<div class="message">' +
'<label><input class="confirm-attendees-decline" type="checkbox" checked="checked" value="1" name="decline" /> ' +
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) {
html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' +
'<div class="savemode">' +
'<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
'<a href="#future" class="button">' + 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().click(function(e){
data._savemode = String(this.href).replace(/.+#/, '');
if ($dialog.find('input.confirm-attendees-donotify').get(0))
data._notify = notify && $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
if (decline && $dialog.find('input.confirm-attendees-decline:checked'))
data.decline = 1;
update_event(action, data);
$dialog.dialog("destroy").hide();
return false;
});
var buttons = [{
text: rcmail.gettext('cancel', 'calendar'),
click: function() {
$(this).dialog("close");
}
}];
if (!event.recurrence) {
buttons.push({
text: rcmail.gettext((action == 'remove' ? 'remove' : 'save'), 'calendar'),
click: function() {
data._notify = notify && $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
data.decline = decline && $dialog.find('input.confirm-attendees-decline:checked').length ? 1 : 0;
update_event(action, data);
$(this).dialog("close");
}
});
}
$dialog.dialog({
modal: true,
width: 460,
dialogClass: 'warning',
title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
buttons: buttons,
close: function(){
$dialog.dialog("destroy").hide();
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'));
}
/*** fullcalendar event handlers ***/
var fc_event_render = function(event, element, view) {
if (view.name != 'list' && view.name != 'table') {
var prefix = event.sensitivity != 0 ? 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">@ ' + Q(event.location) + '</div>');
}
if (event.sensitivity != 0)
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)
element.find('div.fc-event-time').append('<i class="fc-icon-alarms"></i>');
}
};
/*** 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({
header: { left: '', center: '', right: '' },
height: h - 50,
defaultView: 'agendaDay',
date: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone
eventSources: sources,
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'] },
axisFormat : settings['time_format'],
columnFormat: { day: 'dddd ' + settings['date_short'] },
titleFormat: { day: 'dddd ' + settings['date_long'] },
allDayText: rcmail.gettext('all-day', 'calendar'),
currentTimeIndicator: settings.time_indicator,
eventRender: fc_event_render,
eventClick: function(event) {
event_show_dialog(event);
}
});
this.fisheye_date = date;
};
//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');
- var printwin = window.open(rcmail.url('print', { view: view, date: date2unixtime(date), range: range, sections: sections, search: this.search_query }), "rc_print_calendars", "toolbar=no,location=yes,menubar=yes,resizable=yes,scrollbars=yes,width=800");
- window.setTimeout(function(){ printwin.focus() }, 50);
+ 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'),
close: function() {
$dialog.html('').dialog("destroy").hide();
},
buttons: buttons,
minWidth: 400,
width: 420
}).show();
};
this.calendar_remove = function(calendar)
{
if (confirm(rcmail.gettext('deletecalendarconfirm', 'calendar'))) {
rcmail.http_post('calendar', { action:'remove', c:{ id:calendar.id } });
return true;
}
return false;
};
this.calendar_destroy_source = function(id)
{
if (this.calendars[id]) {
fc.fullCalendar('removeEventSource', this.calendars[id]);
$(rcmail.get_folder_li(id, 'rcmlical')).remove();
$('#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');
$('#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);
});
// display upload indicator
me.saving_lock = rcmail.set_busy(true, 'uploading');
}
};
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'),
close: function() {
$dialog.dialog("destroy").hide();
},
buttons: buttons,
width: 520
}).show();
};
// callback from server if import succeeded
this.import_success = function(p)
{
$("#eventsimport:ui-dialog").dialog('close');
rcmail.set_busy(false, null, me.saving_lock);
rcmail.gui_objects.importform.reset();
if (p.refetch)
this.refresh(p);
};
// 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) {
$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];
if (source && (p.refetch || (p.update && !source.active))) {
// activate event source if new event was added to an invisible calendar
if (!source.active) {
source.active = true;
fc.fullCalendar('addEventSource', source);
$('#' + rcmail.get_folder_li(source.id, 'rcmlical').id + ' input').prop('checked', true);
}
else
fc.fullCalendar('refetchEvents', source);
}
// add/update single event object
else if (source && p.update) {
var event = p.update;
event.temp = false;
event.editable = source.editable;
var existing = fc.fullCalendar('clientEvents', event._id);
if (existing.length) {
$.extend(existing[0], event);
fc.fullCalendar('updateEvent', existing[0]);
}
else {
event.source = source; // link with source
fc.fullCalendar('renderEvent', event);
}
// refresh fish-eye view
if (me.fisheye_date)
me.fisheye_view(me.fisheye_date);
}
// remove temp events
fc.fullCalendar('removeEvents', function(e){ return e.temp; });
};
/*** 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 (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='+escape(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 (!?)
};
// 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);
};
/*** startup code ***/
// create list of event sources AKA calendars
this.calendars = {};
var li, cal, active, event_sources = [];
for (var id in rcmail.env.calendars) {
cal = rcmail.env.calendars[id];
this.calendars[id] = $.extend({
url: "./?_task=calendar&_action=load_events&source="+escape(id),
editable: !cal.readonly,
className: 'fc-event-cal-'+id,
id: id
}, cal);
this.calendars[id].color = settings.event_coloring % 2 ? '' : '#' + cal.color;
if ((active = cal.active || false)) {
event_sources.push(this.calendars[id]);
}
// init event handler on calendar list checkbox
if ((li = rcmail.get_folder_li(id, 'rcmlical'))) {
$('#'+li.id+' input').click(function(e){
var id = $(this).data('id');
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;
}
// 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 } });
}
}).data('id', id).get(0).checked = active;
$(li).click(function(e){
var id = $(this).data('id');
rcmail.select_folder(id, 'rcmlical');
rcmail.enable_command('calendar-edit', true);
rcmail.enable_command('calendar-remove', 'events-import', 'calendar-showurl', true);
me.selected_calendar = id;
})
.dblclick(function(){ me.calendar_edit_dialog(me.calendars[me.selected_calendar]); })
.data('id', id);
}
if (!cal.readonly && !this.selected_calendar) {
this.selected_calendar = id;
rcmail.enable_command('addevent', true);
}
}
// select default calendar
if (settings.default_calendar && this.calendars[settings.default_calendar] && !this.calendars[settings.default_calendar].readonly)
this.selected_calendar = settings.default_calendar;
var viewdate = new Date();
if (rcmail.env.date)
viewdate.setTime(fromunixtime(rcmail.env.date));
// initalize the fullCalendar plugin
var fc = $('#calendar').fullCalendar({
header: {
right: 'prev,next today',
center: 'title',
left: 'agendaDay,agendaWeek,month,table'
},
aspectRatio: 1,
date: viewdate.getDate(),
month: viewdate.getMonth(),
year: viewdate.getFullYear(),
ignoreTimezone: true, // will treat the given date strings as in local (browser's) timezone
height: $('#calendar').height(),
eventSources: event_sources,
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: 1, // advance one day 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: (bw.ie6 ? ' << ' : ' ◄ '),
next: (bw.ie6 ? ' >> ' : ' ► '),
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')
},
selectable: true,
selectHelper: false,
currentTimeIndicator: settings.time_indicator,
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);
},
// event rendering
eventRender: fc_event_render,
// 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 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 a specific event is clicked
eventClick: function(event) {
if (!event.temp)
event_show_dialog(event);
},
// 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;
}
},
viewRender: function(view) {
if (fc && view.name == 'month')
fc.fullCalendar('option', 'maxHeight', Math.floor((view.element.parent().height()-18) / 6) - 35);
}
});
// format time string
var formattime = function(hour, minutes, start) {
var time, diff, unit, duration = '', d = new Date();
d.setHours(hour);
d.setMinutes(minutes);
time = $.fullCalendar.formatDate(d, settings['time_format']);
if (start) {
diff = Math.floor((d.getTime() - start.getTime()) / 60000);
if (diff > 0) {
unit = 'm';
if (diff >= 60) {
unit = 'h';
diff = Math.round(diff / 3) / 20;
}
duration = ' (' + diff + unit + ')';
}
}
return [time, duration];
};
var autocomplete_times = function(p, callback) {
/* Time completions */
var result = [];
var now = new Date();
var st, start = (String(this.element.attr('id')).indexOf('endtime') > 0
&& (st = $('#edit-starttime').val())
&& $('#edit-startdate').val() == $('#edit-enddate').val())
? parse_datetime(st, '') : null;
var full = p.term - 1 > 0 || p.term.length > 1;
var hours = start ? start.getHours() :
(full ? parse_datetime(p.term, '') : now).getHours();
var step = 15;
var minutes = hours * 60 + (full ? 0 : now.getMinutes());
var min = Math.ceil(minutes / step) * step % 60;
var hour = Math.floor(Math.ceil(minutes / step) * step / 60);
// list hours from 0:00 till now
for (var h = start ? start.getHours() : 0; h < hours; h++)
result.push(formattime(h, 0, start));
// list 15min steps for the next two hours
for (; h < hour + 2 && h < 24; h++) {
while (min < 60) {
result.push(formattime(h, min, start));
min += step;
}
min = 0;
}
// list the remaining hours till 23:00
while (h < 24)
result.push(formattime((h++), 0, start));
return callback(result);
};
var autocomplete_open = function(event, ui) {
// scroll to current time
var $this = $(this);
var widget = $this.autocomplete('widget');
var menu = $this.data('autocomplete').menu;
var amregex = /^(.+)(a[.m]*)/i;
var pmregex = /^(.+)(a[.m]*)/i;
var val = $(this).val().replace(amregex, '0:$1').replace(pmregex, '1:$1');
var li, html;
widget.css('width', '10em');
widget.children().each(function(){
li = $(this);
html = li.children().first().html().replace(/\s+\(.+\)$/, '').replace(amregex, '0:$1').replace(pmregex, '1:$1');
if (html.indexOf(val) == 0)
menu._scrollIntoView(li);
});
};
// 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: false, // maybe enable?
changeYear: false, // maybe enable?
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
.click(function(e) {
var cell = $(e.target);
if (e.target.tagName == 'TD' && cell.hasClass('ui-datepicker-week-col')) {
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 day_off = base_date.getDay() - datepicker_settings.firstDay;
var base_kw = iso8601Week(base_date);
var target_kw = parseInt(cell.html());
var diff = (target_kw - base_kw) * 7 * DAY_MS;
// select monday of the chosen calendar week
var date = new Date(base_date.getTime() - day_off * DAY_MS + diff);
fc.fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
minical.datepicker('setDate', date);
}
});
// init event dialog
$('#eventtabs').tabs({
show: function(event, ui) {
if (ui.panel.id == 'event-tab-3') {
$('#edit-attendee-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();
}
}
}
});
$('#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')
.attr('autocomplete', "off")
.autocomplete({
delay: 100,
minLength: 1,
source: autocomplete_times,
open: autocomplete_open,
change: event_times_changed,
select: function(event, ui) {
$(this).val(ui.item[0]);
return false;
}
})
.click(function() { // show drop-down upon clicks
$(this).autocomplete('search', $(this).val() ? $(this).val().replace(/\D.*/, "") : " ");
}).each(function(){
$(this).data('autocomplete')._renderItem = function(ul, item) {
return $('<li>')
.data('item.autocomplete', item)
.append('<a>' + item[0] + item[1] + '</a>')
.appendTo(ul);
};
});
// register events on alarm fields
init_alarms_edit('#eventedit');
// toggle recurrence frequency forms
$('#edit-recurrence-frequency').change(function(e){
var freq = $(this).val().toLowerCase();
$('.recurrence-form').hide();
if (freq)
$('#recurrence-form-'+freq+', #recurrence-form-until').show();
});
$('#edit-recurrence-enddate').datepicker(datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) });
$('#edit-recurrence-repeat-times').change(function(e){ $('#edit-recurrence-repeat-count').prop('checked', true); });
// 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){ $('#edit-attendee-add').click(); });
$('#edit-attendee-add').click(function(){
var input = $('#edit-attendee-name');
rcmail.ksearch_blur();
if (add_attendees(input.val())) {
input.val('');
}
});
// keep these two checkboxes in sync
$('#edit-attendees-donotify, #edit-attendees-invite').click(function(){
$('#edit-attendees-donotify, #edit-attendees-invite').prop('checked', this.checked);
});
$('#edit-attendee-schedule').click(function(){
event_freebusy_dialog();
});
$('#shedule-freebusy-prev').html(bw.ie6 ? '<<' : '◄').button().click(function(){ render_freebusy_grid(-1); });
$('#shedule-freebusy-next').html(bw.ie6 ? '>>' : '►').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(){
event_rsvp($(this).attr('rel'))
});
$('#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);
// 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('events-import', function(){ cal.import_events(cal.calendars[cal.selected_calendar]); }, false);
rcmail.register_command('calendar-showurl', function(){ cal.showurl(cal.calendars[cal.selected_calendar]); }, false);
// search and export events
rcmail.register_command('export', function(){ rcmail.goto_url('export_events', { source:cal.selected_calendar }); }, true);
rcmail.register_command('search', function(){ cal.quicksearch(); }, true);
rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
// register callback commands
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); });
// 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 a16f8ff7..f01a477b 100644
--- a/plugins/kolab_files/kolab_files.js
+++ b/plugins/kolab_files/kolab_files.js
@@ -1,1333 +1,1330 @@
/**
* Kolab files plugin
*
* @version @package_version@
* @author Aleksander Machniak <alec@alec.pl>
*/
window.rcmail && rcmail.addEventListener('init', function() {
if (rcmail.task == 'mail') {
// mail compose
if (rcmail.env.action == 'compose') {
var elem = $('#compose-attachments > div'),
input = $('<input class="button" type="button">');
input.val(rcmail.gettext('kolab_files.fromcloud'))
.click(function() { kolab_files_selector_dialog(); })
.appendTo(elem);
if (rcmail.gui_objects.filelist) {
rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, {
multiselect: true,
// draggable: true,
keyboard: true,
column_movable: false,
dblclick_time: rcmail.dblclick_time
});
rcmail.file_list.addEventListener('select', function(o) { kolab_files_list_select(o); });
rcmail.file_list.addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); });
rcmail.gui_objects.filelist.parentNode.onmousedown = function(e){ return kolab_files_click_on_list(e); };
rcmail.enable_command('files-sort', 'files-search', 'files-search-reset', true);
rcmail.file_list.init();
kolab_files_list_coltypes();
}
}
// 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(); })
.appendTo(attachment_list);
}
rcmail.addEventListener('menu-open', kolab_files_attach_menu_open);
}
kolab_files_init();
}
else if (rcmail.task == 'files') {
if (rcmail.gui_objects.filelist) {
rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, {
multiselect: true,
draggable: true,
keyboard: true,
column_movable: rcmail.env.col_movable,
dblclick_time: rcmail.dblclick_time
});
/*
rcmail.file_list.row_init = function(o){ kolab_files_init_file_row(o); };
rcmail.file_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
rcmail.file_list.addEventListener('click', function(o){ p.msglist_click(o); });
rcmail.file_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
rcmail.file_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
rcmail.file_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
*/
rcmail.file_list.addEventListener('dblclick', function(o){ kolab_files_list_dblclick(o); });
rcmail.file_list.addEventListener('select', function(o){ kolab_files_list_select(o); });
rcmail.file_list.addEventListener('dragend', function(e){ kolab_files_drag_end(e); });
rcmail.file_list.addEventListener('column_replace', function(e){ kolab_files_set_coltypes(e); });
rcmail.file_list.addEventListener('listupdate', function(e){ rcmail.triggerEvent('listupdate', e); });
// document.onmouseup = function(e){ return p.doc_mouse_up(e); };
rcmail.gui_objects.filelist.parentNode.onmousedown = function(e){ return kolab_files_click_on_list(e); };
rcmail.enable_command('menu-open', 'menu-save', 'files-sort', 'files-search', 'files-search-reset', true);
rcmail.file_list.init();
kolab_files_list_coltypes();
}
// "one file only" commands
rcmail.env.file_commands = ['files-get'];
// "one or more file" commands
rcmail.env.file_commands_all = ['files-delete', 'files-move', 'files-copy'];
kolab_files_init();
if (rcmail.env.action == 'open') {
rcmail.enable_command('files-get', 'files-delete', rcmail.env.file);
}
else {
file_api.folder_list();
file_api.browser_capabilities_check();
}
}
});
/**********************************************************/
/********* Shared functionality **********/
/**********************************************************/
// Initializes API object
function kolab_files_init()
{
if (window.file_api)
return;
// 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: 'program/resources',
supported_mimetypes: rcmail.env.file_mimetypes
});
file_api.translations = rcmail.labels;
};
// 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) || rcmail.env.files_token;
};
// folder selection dialog
function kolab_directory_selector_dialog(id)
{
var dialog = $('#files-dialog'), buttons = {},
input = $('#file-save-as-input'),
form = $('#file-save-as'),
list = $('#folderlistbox');
// attachment is specified
if (id) {
var attach = $('#attach'+id), filename = attach.attr('title') || attach.text();
form.show();
dialog.addClass('saveas');
input.val(filename);
}
else {
form.hide();
dialog.removeClass('saveas');
}
buttons[rcmail.gettext('kolab_files.save')] = function () {
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);
dialog.dialog('destroy').hide();
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
dialog.dialog('destroy').hide();
};
// show dialog window
dialog.dialog({
modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: rcmail.gettext('kolab_files.' + (id ? 'saveto' : 'saveall')),
// close: function() { rcmail.dialog_close(); },
buttons: buttons,
minWidth: 250,
minHeight: 300,
height: 350,
width: 300
}).show();
if (!rcmail.env.folders_loaded) {
file_api.folder_list();
rcmail.env.folders_loaded = true;
}
};
// file selection dialog
function kolab_files_selector_dialog()
{
var dialog = $('#files-compose-dialog'), buttons = {};
buttons[rcmail.gettext('kolab_files.attachsel')] = function () {
var list = [];
$('#filelist tr.selected').each(function() {
list.push($(this).data('file'));
});
dialog.dialog('destroy').hide();
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 () {
dialog.dialog('destroy').hide();
};
// show dialog window
dialog.dialog({
modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: rcmail.gettext('kolab_files.selectfiles'),
// close: function() { rcmail.dialog_close(); },
buttons: buttons,
minWidth: 500,
minHeight: 300,
width: 700,
height: 500
}).show();
if (!rcmail.env.files_loaded) {
file_api.folder_list();
rcmail.env.files_loaded = true;
}
else
rcmail.file_list.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);
dialog.dialog('destroy').hide();
};
buttons[rcmail.gettext('kolab_files.cancel')] = function () {
dialog.dialog('destroy').hide();
};
// show dialog window
dialog.dialog({
modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
title: rcmail.gettext('kolab_files.foldercreate'),
// close: function() { rcmail.dialog_close(); },
buttons: buttons,
minWidth: 400,
minHeight: 300,
width: 500,
height: 400
}).show();
// build parent selector
select.append($('<option>').val('').text('---'));
$.each(file_api.env.folders, function(i, f) {
var n, option = $('<option>'), name = escapeHTML(f.name);
for (n=0; n<f.depth; n++)
name = ' ' + name;
option.val(i).html(name).appendTo(select);
if (i == file_api.env.folder)
option.attr('selected', true);
});
};
// smart upload button
function kolab_files_upload_input(button)
{
var link = $(button),
file = $('<input>'),
offset = link.offset();
file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5})
.change(function() { rcmail.files_upload('#filesuploadform'); })
// opacity:0 does the trick, display/visibility doesn't work
.css({opacity: 0, cursor: 'pointer', outline: 'none', position: 'absolute', top: '10000px', left: '10000px'});
// 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
link.css({overflow: 'hidden', cursor: 'pointer'})
// place button under the cursor
.mousemove(function(e) {
if (rcmail.commands['files-upload'])
file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'});
// move the input away if button is disabled
else
$(this).mouseleave();
})
.mouseleave(function() { file.css({top: '10000px', left: '10000px'}); })
.attr('onclick', '') // remove default button action
.append(file);
};
/***********************************************************/
/********** Main functionality **********/
/***********************************************************/
// for reordering column array (Konqueror workaround)
// and for setting some message list global variables
kolab_files_list_coltypes = function()
{
var n, list = rcmail.file_list;
rcmail.env.subject_col = null;
if ((n = $.inArray('name', rcmail.env.coltypes)) >= 0) {
rcmail.env.subject_col = n;
list.subject_col = n;
}
list.init_header();
};
kolab_files_set_list_options = function(cols, sort_col, sort_order)
{
var update = 0, i, idx, name, newcols = [], oldcols = rcmail.env.coltypes;
if (sort_col === undefined)
sort_col = rcmail.env.sort_col;
if (!sort_order)
sort_order = rcmail.env.sort_order;
if (rcmail.env.sort_col != sort_col || rcmail.env.sort_order != sort_order) {
update = 1;
rcmail.set_list_sorting(sort_col, sort_order);
}
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('files-list', {sort: sort_col, reverse: sort_order == 'DESC'});
else if (update) {
rcmail.http_post('files/prefs', {
kolab_files_list_cols: oldcols,
kolab_files_sort_col: sort_col,
kolab_files_sort_order: sort_order
}, rcmail.set_busy(true, 'loading'));
}
};
kolab_files_set_coltypes = function(list)
{
var i, found, name, cols = list.list.tHead.rows[0].cells;
rcmail.env.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.coltypes.push(name);
}
// if ((found = $.inArray('name', rcmail.env.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.coltypes});
};
kolab_files_click_on_list = function(e)
{
if (rcmail.gui_objects.qsearchbox)
rcmail.gui_objects.qsearchbox.blur();
if (rcmail.file_list)
rcmail.file_list.focus();
return true;
};
kolab_files_list_dblclick = function(list)
{
rcmail.command('files-open');
};
kolab_files_list_select = function(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;
// enable files-
if (selected == 1) {
// get file mimetype
var type = $('tr.selected', list.list).data('type');
rcmail.env.viewer = file_api.file_type_supported(type);
}
else
rcmail.env.viewer = 0;
/*
) {
// caps = this.browser_capabilities().join();
href = '?' + $.param({_task: 'files', _action: 'open', file: file, viewer: viewer == 2 ? 1 : 0});
var win = window.open(href, rcmail.html_identifier('rcubefile'+file));
if (win)
setTimeout(function() { win.focus(); }, 10);
}
*/
rcmail.enable_command('files-open', rcmail.env.viewer);
};
kolab_files_drag_end = function(e)
{
var folder = $('#files-folder-list li.droptarget').removeClass('droptarget');
if (folder.length) {
folder = folder.data('folder');
var modkey = rcube_event.get_modifier(e),
menu = rcmail.gui_objects.file_dragmenu;
if (menu && modkey == SHIFT_KEY && rcmail.commands['files-copy']) {
var pos = rcube_event.get_mouse_pos(e);
rcmail.env.drag_target = folder;
$(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
return;
}
rcmail.command('files-move', folder);
}
};
kolab_files_drag_menu_action = function(command)
{
var menu = rcmail.gui_objects.file_dragmenu;
if (menu)
$(menu).hide();
rcmail.command(command, rcmail.env.drag_target);
};
kolab_files_selected = function()
{
var files = [];
$.each(rcmail.file_list.get_selection(), function(i, v) {
var name, row = $('#rcmrow'+v);
if (row.length == 1 && (name = row.data('file')))
files.push(name);
});
return files;
};
/***********************************************************/
/********** Commands **********/
/***********************************************************/
rcube_webmail.prototype.files_sort = function(props)
{
var params = {},
sort_order = this.env.sort_order,
sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
if (!this.env.disabled_sort_order)
sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
// set table header and update env
this.set_list_sorting(sort_col, sort_order);
this.http_post('files/prefs', {kolab_files_sort_col: sort_col, kolab_files_sort_order: sort_order});
params.sort = sort_col;
params.reverse = sort_order == 'DESC';
this.command('files-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.file_list;
list.clear();
$('thead', list.list).html(head);
kolab_files_list_coltypes();
file_api.file_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);
};
/**********************************************************/
/********* Files API handler **********/
/**********************************************************/
function kolab_files_ui()
{
/*
// 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)
{
rcmail.http_error(request, status, err);
};
// folders list request
this.folder_list = function()
{
this.req = this.set_busy(true, 'loading');
this.request('folder_list', {}, 'folder_list_response');
};
// folder list response handler
this.folder_list_response = function(response)
{
if (!this.response(response))
return;
var first, elem = $('#files-folder-list'),
list = $('<ul class="listing"></ul>'),
collections = !rcmail.env.action.match(/^(preview|show)$/) ? ['audio', 'video', 'image', 'document'] : [];
elem.html('').append(list);
this.env.folders = this.folder_list_parse(response.result);
$.each(this.env.folders, function(i, f) {
var row = $('<li class="mailbox"><span class="branch"></span></li>');
row.attr('id', f.id).data('folder', i)
.append($('<span class="name">').text(f.name))
.click(function() { file_api.folder_select(i); });
if (f.depth)
$('span.branch', row).width(15 * f.depth);
if (f.virtual)
row.addClass('virtual');
else
row.mouseenter(function() {
if (rcmail.file_list && rcmail.file_list.drag_active && !$(this).hasClass('selected'))
$(this).addClass('droptarget');
})
.mouseleave(function() {
if (rcmail.file_list && rcmail.file_list.drag_active)
$(this).removeClass('droptarget');
});
list.append(row);
if (!first)
first = i;
});
// add virtual collections
$.each(collections, function(i, n) {
var row = $('<li class="mailbox collection ' + n + '"></li>');
row.attr('id', 'folder-collection-' + n)
.append($('<span class="name">').text(rcmail.gettext('kolab_files.collection_' + n)))
.click(function() { file_api.folder_select(n, true); });
list.append(row);
});
// select first folder?
if (this.env.folder)
this.folder_select(this.env.folder);
else if (this.env.collection)
this.folder_select(this.env.collection, true);
else if (first)
this.folder_select(first);
// add tree icons
this.folder_list_tree(this.env.folders);
};
this.folder_select = function(folder, is_collection)
{
var list = $('#files-folder-list > ul');
if (rcmail.busy)
return;
$('li.selected', list).removeClass('selected');
rcmail.enable_command('files-list', true);
if (is_collection) {
var found = $('#folder-collection-' + folder, list).addClass('selected');
rcmail.enable_command('files-folder-delete', 'files-upload', false);
this.env.folder = null;
rcmail.command('files-list', {collection: folder});
}
else {
var found = $('#' + this.env.folders[folder].id, list).addClass('selected');
rcmail.enable_command('files-folder-delete', 'files-upload', true);
this.env.folder = folder;
this.env.collection = null;
rcmail.command('files-list', {folder: folder});
}
};
this.folder_unselect = function()
{
var list = $('#files-folder-list > ul');
$('li.selected', list).removeClass('selected');
rcmail.enable_command('files-folder-delete', 'files-upload', false);
this.env.folder = null;
this.env.collection = null;
};
// 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 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', 'files-folder-rename', 'files-list', false);
this.display_message('kolab_files.folderdeletenotice', 'confirmation');
// refresh folders list
this.folder_list();
};
this.file_list = function(params)
{
if (!rcmail.gui_objects.filelist)
return;
if (!params)
params = {};
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.sort_col;
if (params.reverse == undefined)
params.reverse = this.env.sort_reverse;
if (params.search == undefined)
params.search = this.env.search;
this.env.folder = params.folder;
this.env.collection = params.collection;
this.env.sort_col = params.sort;
this.env.sort_reverse = params.reverse;
rcmail.enable_command(rcmail.env.file_commands, false);
rcmail.enable_command(rcmail.env.file_commands_all, false);
// empty the list
this.env.file_list = [];
rcmail.file_list.clear();
// request
if (params.collection || params.all_folders)
this.file_list_loop(params);
else if (this.env.folder) {
this.req = this.set_busy(true, 'loading');
this.request('file_list', params, 'file_list_response');
}
};
// file list response handler
this.file_list_response = function(response)
{
if (!this.response(response))
return;
var i = 0, list = [], table = $('#filelist');
$.each(response.result, function(key, data) {
var row = file_api.file_list_row(key, data, ++i);
rcmail.file_list.insert_row(row);
data.row = row;
data.filename = key;
list.push(data);
});
this.env.file_list = list;
};
// 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++) {
this.req = this.set_busy(true, 'loading');
params.folder = folders.shift();
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);
for (i=0; i<folders.length && i<limit; i++) {
this.req = this.set_busy(true, 'loading');
params.folder = folders.shift();
this.request('file_list', params, 'file_list_loop_response');
}
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, list = [], rows = [],
index = this.env.file_list.length,
table = rcmail.file_list;
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;
var 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]);
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]);
});
this.env.file_list = list;
this.env.folders_loop_lock = false;
};
// sort files list (without API request)
this.file_list_sort = function(col, reverse)
{
var n, len, list = this.env.file_list,
table = $('#filelist'), tbody = $('<tbody>');
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);
};
this.file_list_row = function(file, data, index)
{
var c, col, row = '';
for (c in rcmail.env.coltypes) {
c = rcmail.env.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"></td>'; // @TODO
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);
};
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');
}
};
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;
this.display_message('kolab_files.filedeletenotice', 'confirmation');
if (rcmail.env.file) {
// @TODO: reload files list in parent window
window.close();
}
else
this.file_list();
};
// 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');
};
// 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
dialog.dialog({
modal: true,
resizable: !bw.ie6,
closeOnEscape: (!bw.ie6 && !bw.ie7), // disable for performance reasons
close: skip_func,
buttons: buttons,
minWidth: 400,
width: 400
}).html(text).show();
};
// 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 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) {
// 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'};
}
rcmail.hide_message(event.data.ts);
// refresh the list on upload success
if (file_api.response_parse(response))
file_api.file_list();
});
}
};
// post the given form to a hidden iframe
this.file_upload_form = function(form, action, onload)
{
var ts = rcmail.display_message(rcmail.get_label('kolab_files.uploading'), 'loading', 1000),
frame_name = 'fileupload'+ts;
/*
// upload progress support
if (this.env.upload_progress_name) {
var fname = this.env.upload_progress_name,
field = $('input[name='+fname+']', form);
if (!field.length) {
field = $('<input>').attr({type: 'hidden', name: fname});
field.prependTo(form);
}
field.val(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="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, uploadid:ts}),
method: 'POST'
}).attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
.submit();
};
// open file in new window, using file API viewer
this.file_open = function(file, viewer)
{
- var href = '?' + $.param({_task: 'files', _action: 'open', file: file, viewer: viewer == 2 ? 1 : 0}),
- win = window.open(href, rcmail.html_identifier('rcubefile'+file));
-
- if (win)
- setTimeout(function() { win.focus(); }, 10);
+ var href = '?' + $.param({_task: 'files', _action: 'open', file: file, viewer: viewer == 2 ? 1 : 0});
+ rcmail.open_window(href, false, true);
};
};
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 530bb25d..f24421fd 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -1,1672 +1,1670 @@
/**
* Client scripts for the Tasklist plugin
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, 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/>.
*/
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_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
};
/* private vars */
var selector = 'all';
var tagsfilter = [];
var filtermask = FILTER_MASK_ALL;
var loadstate = { filter:-1, lists:'', search:null };
var idcount = 0;
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 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;
this.selected_list;
/* public methods */
this.init = init;
this.edit_task = task_edit_dialog;
this.delete_task = delete_task;
this.add_childtask = add_childtask;
this.quicksearch = quicksearch;
this.reset_search = reset_search;
this.list_remove = list_remove;
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 init_alarms_edit = this.init_alarms_edit;
/**
* initialize the tasks UI
*/
function init()
{
// initialize task list selectors
for (var id in me.tasklists) {
if ((li = rcmail.get_folder_li(id, 'rcmlitasklist'))) {
init_tasklist_li(li, id);
}
if (me.tasklists[id].editable && !me.selected_list) {
me.selected_list = id;
rcmail.enable_command('addtask', true);
$(li).click();
}
}
// register server callbacks
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.refresh_task', update_taskitem);
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.reload_data', function(){ list_tasks(null); });
rcmail.addEventListener('plugin.unlock_saving', unlock_saving);
// start loading tasks
fetch_counts();
list_tasks();
// register event handlers for UI elements
$('#taskselector a').click(function(e){
if (!$(this).parent().hasClass('inactive'))
list_tasks(this.href.replace(/^.*#/, ''));
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).click(function(e){
if (e.target.nodeName != 'LI')
return false;
var item = $(e.target),
tag = item.data('value');
// 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', this).removeClass('selected');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
item.addClass('selected');
tagsfilter.push(tag);
}
else if (shift) {
item.removeClass('selected');
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;
})
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
return false;
});
// click-handler on task list items (delegate)
$(rcmail.gui_objects.resultlist).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();
$(e.target).toggleClass('collapsed').html(rec.collapsed ? '▶' : '▼');
rcmail.http_post('tasks/task', { action:'collapse', t:{ id:rec.id, list:rec.list }, collapsed:rec.collapsed?1:0 });
break;
case 'complete':
if (rcmail.busy)
return false;
rec.complete = e.target.checked ? 1 : 0;
li.toggleClass('complete');
save_task(rec, 'edit');
return true;
case 'flagged':
if (rcmail.busy)
return false;
rec.flagged = rec.flagged ? 0 : 1;
li.toggleClass('flagged');
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 || '')) {
rec.date = dateText;
save_task(rec, 'edit');
}
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) {
menu.hide();
}
else {
pos = ref.offset();
pos.top += ref.outerHeight();
pos.left += ref.width() - menu.outerWidth();
menu.css({ top:pos.top+'px', left:pos.left+'px' }).show();
menu.data('refid', id);
me.selected_task = rec;
}
e.bubble = false;
break;
default:
if (e.target.nodeName != 'INPUT')
task_show_dialog(id);
break;
}
return false;
})
.dblclick(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();
}
});
// handle global document clicks: close popup menus
$(document.body).click(clear_popups);
// 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', '')
.click(function(e){
$(input).datepicker('setDate', null).datepicker('hide');
});
}, 1);
}
}, datepicker_settings);
}
/**
* initialize task edit form elements
*/
function init_taskedit()
{
$('#taskedit').tabs();
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 alarm fields
init_alarms_edit('#taskedit');
$('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
$('a.edit-nodate').click(function(){
var sel = $(this).attr('rel');
if (sel) $(sel).val('');
return false;
});
}
/**
* 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)
{
if (rcmail.busy)
return;
if (sel && filter_masks[sel] !== undefined) {
filtermask = filter_masks[sel];
selector = sel;
}
var active = active_lists(),
basefilter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL,
reload = 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');
$('#taskselector li.'+selector).addClass('selected');
}
/**
* 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();
}
/**
* 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);
}
render_tasklist();
append_tags(response.tags || []);
rcmail.set_busy(false, 'loading', ui_loading);
}
/**
*
*/
function render_tasklist()
{
// clear display
var id, rec,
count = 0,
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)) {
render_task(rec);
count++;
}
}
fix_tree_toggles();
if (!count)
msgbox.html(rcmail.gettext('notasksfound','tasklist')).show();
}
/**
* 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')]();
})
}
/**
*
*/
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>').attr('rel', tag).data('value', tag).html(Q(tag)).appendTo(rcmail.gui_objects.tagslist);
});
// re-sort tags list
$(rcmail.gui_objects.tagslist).children('li').sortElements(function(a,b){
return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1;
});
}
/**
*
*/
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+/, '');
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)
{
// handle a list of task records
if ($.isArray(rec)) {
$.each(rec, function(i,r){ update_taskitem(r); });
return;
}
var id = rec.id,
oldid = rec.tempid || id,
oldindex = $.inArray(oldid, listindex),
list = me.tasklists[rec.list];
if (oldindex >= 0)
listindex[oldindex] = id;
else
listindex.push(id);
listdata[id] = rec;
// 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);
if (list.active)
render_task(rec, oldid);
else
$('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).remove();
append_tags(rec.tags || []);
fix_tree_toggles();
}
/**
* Submit the given (changed) task record to the server
*/
function save_task(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;
}
/**
* 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 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" ' + (rec.complete == 1.0 ? 'checked="checked" ' : '') + '/>' +
'<span class="flagged"></span>' +
'<span class="title">' + 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">V</a>'
)
.data('id', rec.id)
.draggable({
revert: 'invalid',
addClasses: false,
cursorAt: { left:-10, top:12 },
helper: draggable_helper,
appendTo: 'body',
start: draggable_start,
stop: draggable_stop,
revertDuration: 300
});
if (rec.complete == 1.0)
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>')
.attr('rel', rec.id)
.addClass('taskitem')
.append((rec.collapsed ? '<span class="childtoggle collapsed">▶' : '<span class="childtoggle expanded">▼') + '</span>')
.append(div)
.append('<ul class="childtasks" style="' + (rec.collapsed ? 'display:none' : '') + '"></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
}
}
/**
* Move the given task item to the right place in the list
*/
function resort_task(rec, li, animated)
{
var dir = 0, index, slice, next_li, next_id, next_rec;
// 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);
});
}
// 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.siblings().each(function(i, elem){
next_li = $(elem);
next_id = next_li.attr('rel');
next_rec = listdata[next_id];
if (next_id == rec.id) {
next_li = null;
return 1; // continue
}
if (next_rec && task_cmp(rec, next_rec) > 0) {
return 1; // continue;
}
else if (next_rec && next_li && task_cmp(rec, next_rec) < 0) {
if (animated) insert_animated(li, next_li);
else li.insertBefore(next_li);
next_li = null;
return false;
}
});
index = $.inArray(next_id, listindex);
if (next_li) {
if (animated) insert_animated(li, null, next_li);
else li.insertAfter(next_li);
index++;
}
// 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)
{
var d = Math.floor(a.complete) - Math.floor(b.complete);
if (!d) d = (b._hasdate-0) - (a._hasdate-0);
if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
return d;
}
/**
*
*/
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 draggable_helper()
{
if (!draghelper)
draghelper = $('<div class="taskitem-draghelper">✔</div>');
return draghelper;
}
function draggable_start(event, ui)
{
$('.taskhead, #rootdroppable, #'+rcmail.gui_objects.folderlist.id+' li').droppable({
hoverClass: 'droptarget',
accept: droppable_accept,
drop: draggable_dropped,
addClasses: false
});
$(this).parent().addClass('dragging');
$('#rootdroppable').show();
}
function draggable_stop(event, ui)
{
$(this).parent().removeClass('dragging');
$('#rootdroppable').hide();
}
function 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 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.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();
});
}
}
}
/**
* Show task details in a dialog
*/
function task_show_dialog(id)
{
var $dialog = $('#taskshow'), rec;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (!(rec = listdata[id]) || clear_popups({}))
return;
me.selected_task = rec;
// fill dialog data
$('#task-parent-title').html(Q(rec.parent_title || '')+' »').css('display', rec.parent_title ? 'block' : 'none');
$('#task-title').html(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-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
var taglist = $('#task-tags')[(rec.tags && rec.tags.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)).data('value', val).appendTo(taglist);
});
}
// 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();
}
}
// define dialog buttons
var buttons = [];
buttons.push({
text: rcmail.gettext('edit','tasklist'),
click: function() {
task_edit_dialog(me.selected_task.id, 'edit');
},
disabled: rcmail.busy
});
buttons.push({
text: rcmail.gettext('delete','tasklist'),
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').first().focus();
},
close: function() {
$dialog.dialog('destroy').appendTo(document.body);
},
buttons: buttons,
minWidth: 500,
width: 580
}).show();
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
}
/**
* Opens the dialog to edit a task
*/
function task_edit_dialog(id, action, presets)
{
$('#taskshow:ui-dialog').dialog('close');
var 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' });
if (rcmail.busy || !list.editable || (action == 'edit' && (!rec || rec.readonly)))
return false;
me.selected_task = $.extend({ alarms:'' }, 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 tasklist = $('#taskedit-tasklist').val(rec.list || 0).prop('disabled', rec.parent_id ? true : false);
// 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', '3')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
$('input.tag', rcmail.gui_objects.edittagline).tagedit({
animSpeed: 100,
allowEdit: false,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: tags, minLength: 0 },
texts: { removeLinkTitle: rcmail.gettext('removetag', 'tasklist') }
});
// set alarm(s)
if (rec.alarms || action != 'new') {
if (typeof rec.alarms == 'string')
rec.alarms = rec.alarms.split(';');
var valarms = rec.alarms || [''];
for (var alarm, i=0; i < valarms.length; i++) {
alarm = String(valarms[i]).split(':');
if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY';
$('#taskedit select.edit-alarm-type').val(alarm[1]);
if (alarm[0].match(/@(\d+)/)) {
var ondate = fromunixtime(parseInt(RegExp.$1));
$('#taskedit select.edit-alarm-offset').val('@');
$('#taskedit input.edit-alarm-date').val(me.format_datetime(ondate, 1));
$('#taskedit input.edit-alarm-time').val(me.format_datetime(ondate, 2));
}
else if (alarm[0].match(/([-+])(\d+)([MHD])/)) {
$('#taskedit input.edit-alarm-value').val(RegExp.$2);
$('#taskedit select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
}
break; // only one alarm is currently supported
}
}
// set correct visibility by triggering onchange handlers
$('#taskedit select.edit-alarm-type, #taskedit select.edit-alarm-offset').change();
// 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('select', 0);
// define dialog buttons
var buttons = {};
buttons[rcmail.gettext('save', 'tasklist')] = function() {
// copy form field contents into task object to save
$.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, list:tasklist }, function(key,input){
me.selected_task[key] = input.val();
});
me.selected_task.tags = [];
me.selected_task.attachments = [];
// do some basic input validation
if (!me.selected_task.title || !me.selected_task.title.length) {
title.focus();
return false;
}
else if (me.selected_task.startdate && me.selected_task.date) {
var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.startdate, datepicker_settings);
var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.date, datepicker_settings);
if (startdate > duedate) {
alert(rcmail.gettext('invalidstartduedates', 'tasklist'));
return false;
}
}
$('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem){
if (elem.value)
me.selected_task.tags.push(elem.value);
});
// serialize alarm settings
var alarm = $('#taskedit select.edit-alarm-type').val();
if (alarm) {
var val, offset = $('#taskedit select.edit-alarm-offset').val();
if (offset == '@')
me.selected_task.alarms = '@' + date2unixtime(parse_datetime($('#taskedit input.edit-alarm-time').val(), $('#taskedit input.edit-alarm-date').val())) + ':' + alarm;
else if ((val = parseInt($('#taskedit input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
me.selected_task.alarms = offset[0] + val + offset[1] + ':' + alarm;
}
// uploaded attachments list
for (var i in rcmail.env.attachments) {
if (i.match(/^rcmfile(.+)/))
me.selected_task.attachments.push(RegExp.$1);
}
// task assigned to a new list
if (me.selected_task.list && me.selected_task.list != rec.list) {
me.selected_task._fromlist = rec.list;
}
me.selected_task.complete = complete.val() / 100;
if (isNaN(me.selected_task.complete))
me.selected_task.complete = null;
if (!me.selected_task.list && list.id)
me.selected_task.list = list.id;
if (save_task(me.selected_task, action))
$dialog.dialog('close');
};
if (action != 'new') {
buttons[rcmail.gettext('delete', 'tasklist')] = function() {
if (delete_task(rec.id))
$dialog.dialog('close');
};
}
buttons[rcmail.gettext('cancel', 'tasklist')] = 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() {
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 qstring = '_id='+urlencode(att.id)+'&_t='+urlencode(rec.recurrence_id||rec.id)+'&_list='+urlencode(rec.list);
// 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) {
- rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubetaskattachment');
- if (rcmail.attachment_win) {
- window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
+ if (rcmail.open_window(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', true, true)) {
return;
}
}
rcmail.goto_url('get-attachment', qstring+'&_download=1', 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);
};
/**
*
*/
var remove_attachment = function(elem, id)
{
$(elem.parentNode).hide();
me.selected_task.deleted_attachments.push(id);
delete rcmail.env.attachments[id];
};
/**
*
*/
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 = [{
text: rcmail.gettext('cancel', 'tasklist'),
click: function() {
$(this).dialog('close');
}
}];
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'),
click: function() {
_delete_task(id, 0);
$(this).dialog('close');
}
});
}
var $dialog = $('<div>').html(html);
$dialog.dialog({
modal: true,
width: 520,
dialogClass: 'warning',
title: rcmail.gettext('deletetask', 'tasklist'),
buttons: buttons,
close: function(){
$dialog.dialog('destroy').hide();
}
}).addClass('tasklist-confirm').show();
return true;
}
/**
* Subfunction to submit the delete command after confirm
*/
function _delete_task(id, mode)
{
var rec = listdata[id],
li = $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).hide();
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('task', { action:'delete', t:{ id:rec.id, list:rec.list }, 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();
}
/**
* Check if the given task matches the current filtermask and tag selection
*/
function match_filter(rec)
{
var match = !filtermask || (filtermask & rec.mask) > 0;
if (match && tagsfilter.length) {
match = rec.tags && rec.tags.length;
for (var i=0; match && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], rec.tags) < 0)
match = false;
}
}
return match;
}
/**
*
*/
function list_edit_dialog(id)
{
var list = me.tasklists[id],
$dialog = $('#tasklistform');
editform = $('#tasklisteditform');
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
if (!list)
list = { name:'', editable:true, showalarms:true };
// fill edit form
var name = $('#taskedit-tasklistame').prop('disabled', !list.editable).val(list.editname || list.name),
alarms = $('#taskedit-showalarms').prop('checked', list.showalarms).get(0),
parent = $('#taskedit-parentfolder').val(list.parentfolder);
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save','tasklist')] = 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;
if (parent.length)
data.parentfolder = $('option:selected', parent).val();
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasklist', { action:(list.id ? 'edit' : 'new'), l:data });
$dialog.dialog('close');
};
buttons[rcmail.gettext('cancel','tasklist')] = 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_remove(id)
{
var list = me.tasklists[id];
if (list && list.editable && confirm(rcmail.gettext('deletelistconfirm', 'tasklist'))) {
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
rcmail.http_post('tasklist', { action:'remove', l:{ id:list.id } });
return true;
}
return false;
}
/**
* Callback from server to finally remove the given list
*/
function destroy_list(prop)
{
var list = me.tasklists[prop.id],
li = rcmail.get_folder_li(prop.id, 'rcmlitasklist');
if (li) {
$(li).remove();
}
if (list) {
list.active = false;
// delete me.tasklists[prop.id];
unlock_saving();
remove_tasks(list.id);
}
}
/**
*
*/
function insert_list(prop)
{
var li = $('<li>').attr('id', 'rcmlitasklist'+prop.id)
.append('<input type="checkbox" name="_list[]" value="'+prop.id+'" checked="checked" />')
.append('<span class="handle"> </span>')
.append('<span class="listname">'+Q(prop.name)+'</span>');
$(rcmail.gui_objects.folderlist).append(li);
me.tasklists[prop.id] = prop;
init_tasklist_li(li.get(0), prop.id);
}
/**
*
*/
function update_list(prop)
{
var id = prop.oldid || prop.id,
li = rcmail.get_folder_li(id, 'rcmlitasklist');
if (me.tasklists[id] && li) {
delete me.tasklists[id];
me.tasklists[prop.id] = prop;
$(li).data('id', prop.id);
$('#'+li.id+' input').data('id', prop.id);
$('.listname', li).html(Q(prop.name));
}
}
/**
* 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('all');
}
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();
}
}
/**** Utility functions ****/
/**
* 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();
}
}
/**
* Hide all open popup menus
*/
function clear_popups(e)
{
var count = 0, target = e.target;
if (target && target.className == 'inner')
target = e.target.parentNode;
$('.popupmenu:visible').each(function(i, elem){
var menu = $(elem), id = elem.id;
if (target.id != id+'link' && (!menu.data('sticky') || !target_overlaps(e.target, elem))) {
menu.hide();
count++;
}
});
return count;
}
/**
* 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 (!?)
};
/**
* Register event handlers on a tasklist (folder) item
*/
function init_tasklist_li(li, id)
{
$('#'+li.id+' input').click(function(e){
var id = $(this).data('id');
if (me.tasklists[id]) { // add or remove event source on click
me.tasklists[id].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:me.tasklists[id].active?1:0 } });
}
}).data('id', id).get(0).checked = me.tasklists[id].active || false;
$(li).click(function(e){
var id = $(this).data('id');
rcmail.select_folder(id, 'rcmlitasklist');
rcmail.enable_command('list-edit', 'list-remove', 'list-import', me.tasklists[id].editable);
me.selected_list = id;
})
.dblclick(function(e){
list_edit_dialog($(this).data('id'));
})
.data('id', id)
.data('type', 'tasklist')
.addClass(me.tasklists[id].editable ? null : 'readonly');
}
// 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;
});
};
})();
/* tasklist plugin UI initialization */
var rctasks;
window.rcmail && rcmail.addEventListener('init', function(evt) {
rctasks = new rcube_tasklist_ui(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_list(); }, 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-remove', function(){ rctasks.list_remove(rctasks.selected_list); }, false);
rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true);
rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true);
rctasks.init();
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Apr 4, 8:09 AM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823147
Default Alt Text
(208 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline