From 95d7fa7c3a5a2ba467294e81a338c8385265cda3 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Sun, 5 Jun 2011 19:08:47 -0600 Subject: [PATCH] Implement calendar operations (create/edit/remove) --- plugins/calendar/TODO | 2 +- plugins/calendar/calendar.js | 107 ++++++++++++++++-- plugins/calendar/calendar.php | 53 +++++++-- plugins/calendar/drivers/calendar_driver.php | 18 +++ .../drivers/database/database_driver.php | 76 ++++++++++++- plugins/calendar/lib/calendar_ui.php | 2 + plugins/calendar/lib/fullcalendar-rc.patch | 11 +- plugins/calendar/lib/js/fullcalendar.js | 2 +- .../calendar/lib/js/jquery.miniColors.min.js | 17 +++ plugins/calendar/localization/de_DE.inc | 2 +- plugins/calendar/localization/en_US.inc | 5 + plugins/calendar/skins/default/calendar.css | 11 +- .../skins/default/images/minicolors-all.png | Bin 0 -> 13370 bytes .../default/images/minicolors-handles.gif | Bin 0 -> 421 bytes .../skins/default/jquery.miniColors.css | 65 +++++++++++ .../skins/default/templates/calendar.html | 30 ++++- 16 files changed, 366 insertions(+), 35 deletions(-) create mode 100644 plugins/calendar/lib/js/jquery.miniColors.min.js create mode 100644 plugins/calendar/skins/default/images/minicolors-all.png create mode 100644 plugins/calendar/skins/default/images/minicolors-handles.gif create mode 100644 plugins/calendar/skins/default/jquery.miniColors.css diff --git a/plugins/calendar/TODO b/plugins/calendar/TODO index 2775202b..6646ce80 100644 --- a/plugins/calendar/TODO +++ b/plugins/calendar/TODO @@ -15,7 +15,7 @@ - List (Agenda) view - Individual days selection + Show list of calendars in a (hideable) drawer - - View: 3.1: Folder list + + View: 3.1: Folder list - View: 3.2: Add / Remove / Rename / Share Folders + View: 3.6: Combined calendar view (Turn calendars on/off) + View: 3.7: Small month overview calendar diff --git a/plugins/calendar/calendar.js b/plugins/calendar/calendar.js index 199a373b..929aa7a4 100644 --- a/plugins/calendar/calendar.js +++ b/plugins/calendar/calendar.js @@ -36,6 +36,7 @@ function rcube_calendar(settings) /*** private vars ***/ var me = this; + var fcselector = '#calendar'; var day_clicked = day_clicked_ts = 0; var ignore_click = false; @@ -158,14 +159,15 @@ function rcube_calendar(settings) minWidth: 320, width: 420 }).show(); - +/* + // add link for "more options" drop-down $('') .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) @@ -461,7 +463,7 @@ function rcube_calendar(settings) ], close: function(){ $dialog.dialog("destroy").hide(); - $('#calendar').fullCalendar('refetchEvents'); + $(fcselector).fullCalendar('refetchEvents'); } }).show(); @@ -475,7 +477,7 @@ function rcube_calendar(settings) this.add_event = function() { if (this.selected_calendar) { var now = new Date(); - var date = $('#calendar').fullCalendar('getDate') || now; + var date = $(fcselector).fullCalendar('getDate') || now; date.setHours(now.getHours()+1); date.setMinutes(0); var end = new Date(date.getTime()); @@ -601,6 +603,82 @@ function rcube_calendar(settings) this.dismiss_link = null; }; + // 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").dialog('close'); + + if (!calendar) + calendar = { name:'', color:'cc0000' }; + + // reset form first + $('#calendarform > form').get(0).reset(); + + var name = $('#calendar-name').val(calendar.name); + var color = $('#calendar-color').val(calendar.color).miniColors('value', calendar.color); + + // dialog buttons + var buttons = {}; + + buttons[rcmail.gettext('save', 'calendar')] = function() { + // 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 = { + name: name.val(), + color: color.val().replace(/^#/, '') + }; + if (calendar.id) + data.id = calendar.id; + + rcmail.http_post('plugin.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, + title: rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'), + close: function() { + $dialog.dialog("destroy").hide(); + }, + buttons: buttons, + minWidth: 400, + width: 420 + }).show(); + + name.select(); + }; + + this.calendar_remove = function(calendar) + { + if (confirm(rcmail.gettext('deletecalendarconfirm', 'calendar'))) { + rcmail.http_post('plugin.calendar', { action:'remove', c:{ id:calendar.id } }); + return true; + } + return false; + }; + + this.calendar_destroy_source = function(id) + { + if (this.calendars[id]) { + $(fcselector).fullCalendar('removeEventSource', this.calendars[id]); + $(rcmail.get_folder_li(id, 'rcmlical')).remove(); + $('#edit-calendar option[value="'+id+'"]').remove(); + delete this.calendars[id]; + } + }; /*** startup code ***/ @@ -633,7 +711,7 @@ function rcube_calendar(settings) action = 'removeEventSource'; settings.hidden_calendars.push(id); } - $('#calendar').fullCalendar(action, me.calendars[id]); + $(fcselector).fullCalendar(action, me.calendars[id]); rcmail.save_pref({ name:'hidden_calendars', value:settings.hidden_calendars.join(',') }); } }).data('id', id).get(0).checked = active; @@ -641,6 +719,7 @@ function rcube_calendar(settings) $(li).click(function(e){ var id = $(this).data('id'); rcmail.select_folder(id, me.selected_calendar, 'rcmlical'); + rcmail.enable_command('plugin.calendar-edit','plugin.calendar-remove', true); me.selected_calendar = id; }).data('id', id); } @@ -652,7 +731,7 @@ function rcube_calendar(settings) } // initalize the fullCalendar plugin - $('#calendar').fullCalendar({ + $(fcselector).fullCalendar({ header: { left: 'prev,next today', center: 'title', @@ -802,7 +881,7 @@ function rcube_calendar(settings) var diff = (kw - base_kw) * 7 * 86400000; // select monday of the chosen calendar week var date = new Date(base_date.getTime() - day_off * 86400000 + diff); - $('#calendar').fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek'); + $(fcselector).fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek'); $("#datepicker").datepicker('setDate', date); window.setTimeout(init_week_events, 10); }).css('cursor', 'pointer'); @@ -817,7 +896,7 @@ function rcube_calendar(settings) onSelect: function(dateText, inst) { ignore_click = true; var d = $("#datepicker").datepicker('getDate'); //parse_datetime('0:0', dateText); - $('#calendar').fullCalendar('gotoDate', d).fullCalendar('select', d, d, true); + $(fcselector).fullCalendar('gotoDate', d).fullCalendar('select', d, d, true); window.setTimeout(init_week_events, 10); }, onChangeMonthYear: function(year, month, inst) { @@ -826,14 +905,14 @@ function rcube_calendar(settings) d.setYear(year); d.setMonth(month - 1); $("#datepicker").data('year', year).data('month', month); - //$('#calendar').fullCalendar('gotoDate', d).fullCalendar('setDate', d); + //$(fcselector).fullCalendar('gotoDate', d).fullCalendar('setDate', d); }, })); window.setTimeout(init_week_events, 10); // react on fullcalendar buttons var fullcalendar_update = function() { - var d = $('#calendar').fullCalendar('getDate'); + var d = $(fcselector).fullCalendar('getDate'); $("#datepicker").datepicker('setDate', d); window.setTimeout(init_week_events, 10); }; @@ -930,6 +1009,8 @@ function rcube_calendar(settings) $('#recurrence-form-'+freq+', #recurrence-form-until').show(); }); $('#edit-recurrence-enddate').datepicker(datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) }); + + $('#calendar-color').miniColors(); // hide event dialog when clicking somewhere into document $(document).bind('mousedown', dialog_check); @@ -942,6 +1023,11 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // configure toobar buttons rcmail.register_command('plugin.addevent', function(){ cal.add_event(); }, true); + + // configure list operations + rcmail.register_command('plugin.calendar-create', function(){ cal.calendar_edit_dialog(null); }, true); + rcmail.register_command('plugin.calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false); + rcmail.register_command('plugin.calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false); // export events rcmail.register_command('plugin.export', function(){ rcmail.goto_url('plugin.export_events', { source:cal.selected_calendar }); }, true); @@ -950,6 +1036,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // register callback commands rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); }); rcmail.addEventListener('plugin.reload_calendar', function(p){ $('#calendar').fullCalendar('refetchEvents', cal.calendars[p.source]); }); + rcmail.addEventListener('plugin.calendar_destroy_source', function(p){ cal.calendar_destroy_source(p.id); }); // let's go diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 40f00f0c..bec70d15 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -50,7 +50,7 @@ class calendar extends rcube_plugin } // load localizations - $this->add_texts('localization/', $this->rc->action != 'plugin.event'); + $this->add_texts('localization/', !$this->rc->action || $this->rc->task != 'calendar'); // load Calendar user interface which includes jquery-ui $this->require_plugin('jqueryui'); @@ -71,9 +71,9 @@ class calendar extends rcube_plugin // register calendar actions $this->register_action('index', array($this, 'calendar_view')); - $this->register_action('plugin.calendar', array($this, 'calendar_view')); + $this->register_action('plugin.event', array($this, 'event_action')); + $this->register_action('plugin.calendar', array($this, 'calendar_action')); $this->register_action('plugin.load_events', array($this, 'load_events')); - $this->register_action('plugin.event', array($this, 'event')); $this->register_action('plugin.export_events', array($this, 'export_events')); $this->register_action('plugin.randomdata', array($this, 'generate_randomdata')); $this->add_hook('keep_alive', array($this, 'keep_alive')); @@ -247,7 +247,7 @@ class calendar extends rcube_plugin $field_class = 'rcmfd_category_' . str_replace(' ', '_', $name); $category_remove = new html_inputfield(array('type' => 'button', 'value' => 'X', 'class' => 'button', 'onclick' => '$(this).parent().remove()', 'title' => $this->gettext('remove_category'))); $category_name = new html_inputfield(array('name' => "_categories[$key]", 'class' => $field_class, 'size' => 30)); - $category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => $field_class, 'size' => 6)); + $category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => "$field_class colors", 'size' => 6)); $categories_list .= html::div(null, $category_name->show($name) . ' ' . $category_color->show($color) . ' ' . $category_remove->show()); } @@ -261,16 +261,22 @@ class calendar extends rcube_plugin $p['blocks']['categories']['options']['categories'] = array( 'content' => $new_category->show('') . ' ' . $add_category->show(), ); - + $this->rc->output->add_script('function rcube_calendar_add_category(){ var name = $("#rcmfd_new_category").val(); if (name.length) { var input = $("").attr("type", "text").attr("name", "_categories[]").attr("size", 30).val(name); - var color = $("").attr("type", "text").attr("name", "_colors[]").attr("size", 6).val("000000"); + var color = $("").attr("type", "text").attr("name", "_colors[]").attr("size", 6).addClass("colors").val("000000"); var button = $("").attr("type", "button").attr("value", "X").addClass("button").click(function(){ $(this).parent().remove() }); $("
").append(input).append(" ").append(color).append(" ").append(button).appendTo("#calendarcategories"); + color.miniColors(); } }'); + + // include color picker + $this->include_script('lib/js/jquery.miniColors.min.js'); + $this->include_stylesheet('skins/' .$this->rc->config->get('skin') . '/jquery.miniColors.css'); + $this->rc->output->add_script('$("input.colors").miniColors()', 'docready'); } } @@ -334,10 +340,43 @@ class calendar extends rcube_plugin return $p; } + /** + * Dispatcher for calendar actions initiated by the client + */ + function calendar_action() + { + $action = get_input_value('action', RCUBE_INPUT_POST); + $cal = get_input_value('c', RCUBE_INPUT_POST); + $success = $reload = false; + + switch ($action) { + case "new": + $success = $this->driver->create_calendar($cal); + $reload = true; + break; + case "edit": + $success = $this->driver->edit_calendar($cal); + $reload = true; + break; + case "remove": + if ($success = $this->driver->remove_calendar($cal)) + $this->rc->output->command('plugin.calendar_destroy_source', array('id' => $cal['id'])); + break; + } + + if ($success) + $this->rc->output->show_message('successfullysaved', 'confirmation'); + else + $this->rc->output->show_message('calendar.errorsaving', 'error'); + + if ($success && $reload) + $this->rc->output->redirect(''); + } + /** * Dispatcher for event actions initiated by the client */ - function event() + function event_action() { $action = get_input_value('action', RCUBE_INPUT_POST); $event = get_input_value('e', RCUBE_INPUT_POST); diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index be50e300..b0687728 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -81,6 +81,24 @@ abstract class calendar_driver */ abstract function create_calendar($prop); + /** + * Update properties of an existing calendar + * + * @param array Hash array with calendar properties + * id: Calendar Identifier + * name: Calendar name + * color: The color of the calendar + * @return boolean True on success, Fales on failure + */ + abstract function edit_calendar($prop); + + /** + * Delete the given calendar with all its contents + * + * @return boolean True on success, Fales on failure + */ + abstract function remove_calendar($prop); + /** * Add a single event to the database * diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index ef807747..6394849a 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -75,7 +75,7 @@ class database_driver extends calendar_driver if (!empty($this->rc->user->ID)) { $calendar_ids = array(); $result = $this->rc->db->query( - "SELECT * FROM " . $this->db_calendars . " + "SELECT *, calendar_id AS id FROM " . $this->db_calendars . " WHERE user_id=?", $this->rc->user->ID ); @@ -121,10 +121,59 @@ class database_driver extends calendar_driver ); if ($result) - return $this->rc->db->insert_id($this->$sequence_calendars); + return $this->rc->db->insert_id($this->sequence_calendars); return false; } + + /** + * Update properties of an existing calendar + * + * @see calendar_driver::edit_calendar() + */ + public function edit_calendar($prop) + { + $query = $this->rc->db->query( + "UPDATE " . $this->db_calendars . " + SET name=?, color=? + WHERE calendar_id=? + AND user_id=?", + $prop['name'], + $prop['color'], + $prop['id'], + $this->rc->user->ID + ); + + return $this->rc->db->affected_rows($query); + } + + /** + * Delete the given calendar with all its contents + * + * @see calendar_driver::remove_calendar() + */ + public function remove_calendar($prop) + { + if (!$this->calendars[$prop['id']]) + return false; + + // delete all events of this calendar + $query = $this->rc->db->query( + "DELETE FROM " . $this->db_events . " + WHERE calendar_id=?", + $prop['id'] + ); + + // TODO: also delete linked attachments + + $query = $this->rc->db->query( + "DELETE FROM " . $this->db_calendars . " + WHERE calendar_id=?", + $prop['id'] + ); + + return $this->rc->db->affected_rows($query); + } /** * Add a single event to the database @@ -699,8 +748,15 @@ class database_driver extends calendar_driver */ public function remove_category($name) { - // TBD. alter events accordingly - return false; + $query = $this->rc->db->query( + "UPDATE " . $this->db_events . " + SET categories='' + WHERE categories=? + AND calendar_id IN (" . $this->calendar_ids . ")", + $name + ); + + return $this->rc->db->affected_rows($query); } /** @@ -708,8 +764,16 @@ class database_driver extends calendar_driver */ public function replace_category($oldname, $name, $color) { - // TBD. alter events accordingly - return false; + $query = $this->rc->db->query( + "UPDATE " . $this->db_events . " + SET categories=? + WHERE categories=? + AND calendar_id IN (" . $this->calendar_ids . ")", + $name, + $oldname + ); + + return $this->rc->db->affected_rows($query); } } diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index 1f7348fc..27914e58 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -54,6 +54,7 @@ class calendar_ui { $skin = $this->rc->config->get('skin'); $this->calendar->include_stylesheet('skins/' . $skin . '/fullcalendar.css'); + $this->calendar->include_stylesheet('skins/' . $skin . '/jquery.miniColors.css'); } /** @@ -62,6 +63,7 @@ class calendar_ui public function addJS() { $this->calendar->include_script('lib/js/fullcalendar.js'); + $this->calendar->include_script('lib/js/jquery.miniColors.min.js'); $this->calendar->include_script('calendar.js'); } diff --git a/plugins/calendar/lib/fullcalendar-rc.patch b/plugins/calendar/lib/fullcalendar-rc.patch index 8b5337d1..3a7543d1 100644 --- a/plugins/calendar/lib/fullcalendar-rc.patch +++ b/plugins/calendar/lib/fullcalendar-rc.patch @@ -1,5 +1,5 @@ ---- js/fullcalendar.js.orig 2011-06-04 15:45:44.000000000 -0400 -+++ js/fullcalendar.js 2011-06-04 15:46:38.000000000 -0400 +--- js/fullcalendar.js.orig 2011-06-04 13:45:44.000000000 -0600 ++++ js/fullcalendar.js 2011-06-05 18:58:59.000000000 -0600 @@ -500,8 +500,8 @@ } @@ -11,7 +11,7 @@ } -@@ -897,7 +897,7 @@ +@@ -897,15 +897,16 @@ } @@ -20,9 +20,10 @@ rangeStart = start; rangeEnd = end; cache = []; -@@ -905,7 +905,8 @@ + var fetchID = ++currentFetchID; var len = sources.length; - pendingSourceCnt = len; +- pendingSourceCnt = len; ++ pendingSourceCnt = typeof src == 'undefined' ? len : 1; for (var i=0; i');trigger.insertAfter(input);input.addClass('miniColors').attr('maxlength',7).attr('autocomplete','off');input.data('trigger',trigger);input.data('hsb',hsb);if(o.change)input.data('change',o.change);if(o.readonly)input.attr('readonly',true);if(o.disabled)disable(input);trigger.bind('click.miniColors',function(event){event.preventDefault();input.trigger('focus');});input.bind('focus.miniColors',function(event){show(input);});input.bind('blur.miniColors',function(event){var hex=cleanHex(input.val());input.val(hex?'#'+hex:'');});input.bind('keydown.miniColors',function(event){if(event.keyCode===9)hide(input);});input.bind('keyup.miniColors',function(event){var filteredHex=input.val().replace(/[^A-F0-9#]/ig,'');input.val(filteredHex);if(!setColorFromInput(input)){input.data('trigger').css('backgroundColor','#FFF');}});input.bind('paste.miniColors',function(event){setTimeout(function(){input.trigger('keyup');},5);});};var destroy=function(input){hide();input=$(input);input.data('trigger').remove();input.removeAttr('autocomplete');input.removeData('trigger');input.removeData('selector');input.removeData('hsb');input.removeData('huePicker');input.removeData('colorPicker');input.removeData('mousebutton');input.removeData('moving');input.unbind('click.miniColors');input.unbind('focus.miniColors');input.unbind('blur.miniColors');input.unbind('keyup.miniColors');input.unbind('keydown.miniColors');input.unbind('paste.miniColors');$(document).unbind('mousedown.miniColors');$(document).unbind('mousemove.miniColors');};var enable=function(input){input.attr('disabled',false);input.data('trigger').css('opacity',1);};var disable=function(input){hide(input);input.attr('disabled',true);input.data('trigger').css('opacity',.5);};var show=function(input){if(input.attr('disabled'))return false;hide();var selector=$('
');selector.append('
');selector.append('
');selector.css({top:input.is(':visible')?input.offset().top+input.outerHeight():input.data('trigger').offset().top+input.data('trigger').outerHeight(),left:input.is(':visible')?input.offset().left:input.data('trigger').offset().left,display:'none'}).addClass(input.attr('class'));var hsb=input.data('hsb');selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));var colorPosition=input.data('colorPosition');if(!colorPosition)colorPosition=getColorPositionFromHSB(hsb);selector.find('.miniColors-colorPicker').css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=input.data('huePosition');if(!huePosition)huePosition=getHuePositionFromHSB(hsb);selector.find('.miniColors-huePicker').css('top',huePosition.y+'px');input.data('selector',selector);input.data('huePicker',selector.find('.miniColors-huePicker'));input.data('colorPicker',selector.find('.miniColors-colorPicker'));input.data('mousebutton',0);$('BODY').append(selector);selector.fadeIn(100);selector.bind('selectstart',function(){return false;});$(document).bind('mousedown.miniColors',function(event){input.data('mousebutton',1);if($(event.target).parents().andSelf().hasClass('miniColors-colors')){event.preventDefault();input.data('moving','colors');moveColor(input,event);} +if($(event.target).parents().andSelf().hasClass('miniColors-hues')){event.preventDefault();input.data('moving','hues');moveHue(input,event);} +if($(event.target).parents().andSelf().hasClass('miniColors-selector')){event.preventDefault();return;} +if($(event.target).parents().andSelf().hasClass('miniColors'))return;hide(input);});$(document).bind('mouseup.miniColors',function(event){input.data('mousebutton',0);input.removeData('moving');});$(document).bind('mousemove.miniColors',function(event){if(input.data('mousebutton')===1){if(input.data('moving')==='colors')moveColor(input,event);if(input.data('moving')==='hues')moveHue(input,event);}});};var hide=function(input){if(!input)input='.miniColors';$(input).each(function(){var selector=$(this).data('selector');$(this).removeData('selector');$(selector).fadeOut(100,function(){$(this).remove();});});$(document).unbind('mousedown.miniColors');$(document).unbind('mousemove.miniColors');};var moveColor=function(input,event){var colorPicker=input.data('colorPicker');colorPicker.hide();var position={x:event.clientX-input.data('selector').find('.miniColors-colors').offset().left+$(document).scrollLeft()-5,y:event.clientY-input.data('selector').find('.miniColors-colors').offset().top+$(document).scrollTop()-5};if(position.x<=-5)position.x=-5;if(position.x>=144)position.x=144;if(position.y<=-5)position.y=-5;if(position.y>=144)position.y=144;input.data('colorPosition',position);colorPicker.css('left',position.x).css('top',position.y).show();var s=Math.round((position.x+5)*.67);if(s<0)s=0;if(s>100)s=100;var b=100-Math.round((position.y+5)*.67);if(b<0)b=0;if(b>100)b=100;var hsb=input.data('hsb');hsb.s=s;hsb.b=b;setColor(input,hsb,true);};var moveHue=function(input,event){var huePicker=input.data('huePicker');huePicker.hide();var position={y:event.clientY-input.data('selector').find('.miniColors-colors').offset().top+$(document).scrollTop()-1};if(position.y<=-1)position.y=-1;if(position.y>=149)position.y=149;input.data('huePosition',position);huePicker.css('top',position.y).show();var h=Math.round((150-position.y-1)*2.4);if(h<0)h=0;if(h>360)h=360;var hsb=input.data('hsb');hsb.h=h;setColor(input,hsb,true);};var setColor=function(input,hsb,updateInputValue){input.data('hsb',hsb);var hex=hsb2hex(hsb);if(updateInputValue)input.val('#'+hex);input.data('trigger').css('backgroundColor','#'+hex);if(input.data('selector'))input.data('selector').find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));if(input.data('change')){input.data('change').call(input,'#'+hex,hsb2rgb(hsb));}};var setColorFromInput=function(input){var hex=cleanHex(input.val());if(!hex)return false;var hsb=hex2hsb(hex);var currentHSB=input.data('hsb');if(hsb.h===currentHSB.h&&hsb.s===currentHSB.s&&hsb.b===currentHSB.b)return true;var colorPosition=getColorPositionFromHSB(hsb);var colorPicker=$(input.data('colorPicker'));colorPicker.css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=getHuePositionFromHSB(hsb);var huePicker=$(input.data('huePicker'));huePicker.css('top',huePosition.y+'px');setColor(input,hsb,false);return true;};var getColorPositionFromHSB=function(hsb){var x=Math.ceil(hsb.s/.67);if(x<0)x=0;if(x>150)x=150;var y=150-Math.ceil(hsb.b/.67);if(y<0)y=0;if(y>150)y=150;return{x:x-5,y:y-5};} +var getHuePositionFromHSB=function(hsb){var y=150-(hsb.h/2.4);if(y<0)h=0;if(y>150)h=150;return{y:y-1};} +var cleanHex=function(hex){hex=hex.replace(/[^A-Fa-f0-9]/,'');if(hex.length==3){hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];} +return hex.length===6?hex:null;};var hsb2rgb=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s==0){rgb.r=rgb.g=rgb.b=v;}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h==360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3;} +else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3;} +else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3;} +else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3;} +else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3;} +else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3;} +else{rgb.r=0;rgb.g=0;rgb.b=0;}} +return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)};};var rgb2hex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length==1)hex[nr]='0'+val;});return hex.join('');};var hex2rgb=function(hex){var hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)};};var rgb2hsb=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!=0?255*delta/max:0;if(hsb.s!=0){if(rgb.r==max){hsb.h=(rgb.g-rgb.b)/delta;}else if(rgb.g==max){hsb.h=2+(rgb.b-rgb.r)/delta;}else{hsb.h=4+(rgb.r-rgb.g)/delta;}}else{hsb.h=-1;} +hsb.h*=60;if(hsb.h<0){hsb.h+=360;} +hsb.s*=100/255;hsb.b*=100/255;return hsb;};var hex2hsb=function(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb;};var hsb2hex=function(hsb){return rgb2hex(hsb2rgb(hsb));};switch(o){case'readonly':$(this).each(function(){$(this).attr('readonly',data);});return $(this);break;case'disabled':$(this).each(function(){if(data){disable($(this));}else{enable($(this));}});return $(this);case'value':$(this).each(function(){$(this).val(data).trigger('keyup');});return $(this);break;case'destroy':$(this).each(function(){destroy($(this));});return $(this);default:if(!o)o={};$(this).each(function(){if($(this)[0].tagName.toLowerCase()!=='input')return;if($(this).data('trigger'))return;create($(this),o,data);});return $(this);}}});})(jQuery); \ No newline at end of file diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc index f9d0881d..c1a6e0d4 100644 --- a/plugins/calendar/localization/de_DE.inc +++ b/plugins/calendar/localization/de_DE.inc @@ -17,7 +17,7 @@ $labels = array(); // config $labels['default_view'] = 'Ansicht'; $labels['time_format'] = 'Zeitformatierung'; -$labels['timeslots'] = 'Zeitfenster pro Stunde'; +$labels['timeslots'] = 'Zeitraster pro Stunde'; $labels['first_day'] = 'Erster Wochentag'; // calendar diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 8872f798..f44f5317 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -17,6 +17,9 @@ $labels['calendars'] = 'Calendars'; $labels['category'] = 'Category'; $labels['categories'] = 'Categories'; $labels['createcalendar'] = 'Create new calendar'; +$labels['editcalendar'] = 'Edit calendar properties'; +$labels['name'] = 'Name'; +$labels['color'] = 'Color'; $labels['day'] = 'Day'; $labels['week'] = 'Week'; $labels['month'] = 'Month'; @@ -87,9 +90,11 @@ $labels['tabattachments'] = 'Attachments'; // messages $labels['deleteventconfirm'] = "Do you really want to delete this event?"; +$labels['deletecalendarconfirm'] = "Do you really want to delete this calendar with all its events?"; $labels['errorsaving'] = "Failed to save changes"; $labels['operationfailed'] = "The requested operation failed"; $labels['invalideventdates'] = "Invalid dates entered! Please check your input."; +$labels['invalidcalendarproperties'] = "Invalid calendar properties! Please set a valid name."; // recurrence form $labels['repeat'] = 'Repeat'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index 975b9d63..56846e93 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -207,7 +207,8 @@ pre { } #eventshow, -#eventedit { +#eventedit, +#calendarform { display: none; } @@ -219,6 +220,10 @@ pre { text-align: center; } +a.miniColors-trigger { + margin-top: -3px; +} + /* jQuery UI overrides */ #eventshow h1 { @@ -256,6 +261,7 @@ pre { border-radius: 0; } +div.form-section, #eventshow div.event-section, #eventtabs div.event-section { margin-top: 0.2em; @@ -287,7 +293,8 @@ pre { } #eventshow label, -#eventedit label { +#eventedit label, +.form-section label { display: inline-block; min-width: 7em; padding-right: 0.5em; diff --git a/plugins/calendar/skins/default/images/minicolors-all.png b/plugins/calendar/skins/default/images/minicolors-all.png new file mode 100644 index 0000000000000000000000000000000000000000..001ed888c232598a57e83ea746ecab7b6772674d GIT binary patch literal 13370 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 P6*7I245

- - + +
@@ -27,6 +27,13 @@
+
+
    +
  • +
  • +
+
+

Event Title

Location
@@ -163,9 +170,24 @@
+ +
+
+
+ + +
+
+ + +
+
+
+
+
@@ -183,6 +205,10 @@