From: Jan Schneider Date: Sat, 28 Nov 2009 17:16:14 +0000 (+0100) Subject: Color task lists instead of categories. Only used in Kronolith so far. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=6fbd3f6f10f16056140bd10c267eb02e374ca5ca;p=horde.git Color task lists instead of categories. Only used in Kronolith so far. Add calendar management. Beside a few small featurettes, the Ajax interface is feature complete for the first release now. --- diff --git a/kronolith/ajax.php b/kronolith/ajax.php index c8e7f53c7..411dae566 100644 --- a/kronolith/ajax.php +++ b/kronolith/ajax.php @@ -469,6 +469,195 @@ try { $result->events = 'Searched for calendars: ' . Horde_Util::getFormData('title'); break; + case 'SaveCalendar': + $calendar_id = Horde_Util::getFormData('calendar'); + $result = new stdClass; + + switch (Horde_Util::getFormData('type')) { + case 'internal': + $info = array(); + foreach (array('name', 'color', 'description', 'tags') as $key) { + $info[$key] = Horde_Util::getFormData($key); + } + + // Create a calendar. + if (!$calendar_id) { + if (!Horde_Auth::getAuth() || $prefs->isLocked('default_share')) { + break 2; + } + $calendar = Kronolith::addShare($info); + if ($calendar instanceof PEAR_Error) { + $notification->push($calendar, 'horde.error'); + break 2; + } + $notification->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success'); + $result->calendar = $calendar->getName(); + break; + } + + // Update a calendar. + $calendar = $kronolith_shares->getShare($calendar_id); + if ($calendar instanceof PEAR_Error) { + $notification->push($calendar, 'horde.error'); + break 2; + } + $original_name = $calendar->get('name'); + $updated = Kronolith::updateShare($calendar, $info); + if ($updated instanceof PEAR_Error) { + $notification->push($updated, 'horde.error'); + break 2; + } + if ($calendar->get('name') != $original_name) { + $notification->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success'); + } else { + $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success'); + } + + break; + + case 'tasklists': + $calendar = array(); + foreach (array('name', 'color', 'description') as $key) { + $calendar[$key] = Horde_Util::getFormData($key); + } + + // Create a task list. + if (!$calendar_id) { + if (!Horde_Auth::getAuth() || $prefs->isLocked('default_share')) { + break 2; + } + $tasklist = $registry->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']); + if ($tasklist instanceof PEAR_Error) { + $notification->push($tasklist, 'horde.error'); + break 2; + } + $notification->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success'); + $result->calendar = $tasklist; + break; + } + + // Update a task list. + $calendar_id = substr($calendar_id, 6); + $tasklists = $registry->tasks->listTasklists(true, Horde_Perms::EDIT); + if (!isset($tasklists[$calendar_id])) { + $notification->push(_("You are not allowed to change this task list."), 'horde.error'); + break 2; + } + $updated = $registry->tasks->updateTasklist($calendar_id, $calendar); + if ($updated instanceof PEAR_Error) { + $notification->push($updated, 'horde.error'); + break 2; + } + if ($tasklists[$calendar_id]->get('name') != $calendar['name']) { + $notification->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklists[$calendar_id]->get('name'), $calendar['name']), 'horde.success'); + } else { + $notification->push(sprintf(_("The task list \"%s\" has been saved."), $tasklists[$calendar_id]->get('name')), 'horde.success'); + } + + break; + + case 'remote': + $calendar = array(); + foreach (array('name', 'description', 'url', 'color', 'username', 'password') as $key) { + $calendar[$key] = Horde_Util::getFormData($key); + } + $subscribed = Kronolith::subscribeRemoteCalendar($calendar); + if ($subscribed instanceof PEAR_Error) { + $notification->push($subscribed, 'horde.error'); + break 2; + } + if ($calendar_id) { + $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success'); + } else { + $notification->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success'); + } + break; + } + + $result->saved = true; + $result->color = Kronolith::foregroundColor($calendar); + break; + + case 'DeleteCalendar': + $calendar_id = Horde_Util::getFormData('calendar'); + + switch (Horde_Util::getFormData('type')) { + case 'internal': + $calendar = $kronolith_shares->getShare($calendar_id); + if ($calendar instanceof PEAR_Error) { + $notification->push($calendar, 'horde.error'); + break 2; + } + $deleted = Kronolith::deleteShare($calendar); + if ($deleted instanceof PEAR_Error) { + $notification->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $deleted->getMessage()), 'horde.error'); + break 2; + } + $notification->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success'); + break; + + case 'tasklists': + $calendar_id = substr($calendar_id, 6); + $tasklists = $registry->tasks->listTasklists(true); + if (!isset($tasklists[$calendar_id])) { + $notification->push(_("You are not allowed to delete this task list."), 'horde.error'); + break 2; + } + $deleted = $registry->tasks->deleteTasklist($calendar_id); + if ($deleted instanceof PEAR_Error) { + $notification->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $deleted->getMessage()), 'horde.error'); + break 2; + } + $notification->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success'); + break; + + case 'remote': + $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id); + if ($deleted instanceof PEAR_Error) { + $notification->push($deleted, 'horde.error'); + break 2; + } + $notification->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success'); + break; + } + + $result = new stdClass; + $result->deleted = true; + break; + + case 'GetRemoteInfo': + $params = array(); + if ($user = Horde_Util::getFormData('username')) { + $params['user'] = $user; + $params['password'] = Horde_Util::getFormData('password'); + } + if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) { + $params['proxy'] = $GLOBALS['conf']['http']['proxy']; + } + $driver = Kronolith_Driver::factory('Ical', $params); + $driver->open(Horde_Util::getFormData('url')); + $ical = $driver->getRemoteCalendar(false); + if ($ical instanceof PEAR_Error) { + if ($ical->getCode() == 401) { + $result = new stdClass; + $result->auth = true; + break; + } + $notification->push($ical, 'horde.error'); + break; + } + $result = new stdClass; + $result->success = true; + $name = $ical->getAttribute('X-WR-CALNAME'); + if (!($name instanceof PEAR_Error)) { + $result->name = $name; + } + $desc = $ical->getAttribute('X-WR-CALDESC'); + if (!($desc instanceof PEAR_Error)) { + $result->desc = $desc; + } + break; + case 'SaveCalPref': break; diff --git a/kronolith/calendars/delete.php b/kronolith/calendars/delete.php index 9dc3ab794..33a5eddb3 100644 --- a/kronolith/calendars/delete.php +++ b/kronolith/calendars/delete.php @@ -18,24 +18,12 @@ if (!Horde_Auth::getAuth()) { } $vars = Horde_Variables::getDefaultVariables(); -$calendar_id = $vars->get('c'); -if ($calendar_id == Horde_Auth::getAuth()) { - $notification->push(_("This calendar cannot be deleted."), 'horde.warning'); - header('Location: ' . Horde::applicationUrl('calendars/', true)); - exit; -} - -$calendar = $kronolith_shares->getShare($calendar_id); +$calendar = $kronolith_shares->getShare($vars->get('c')); if (is_a($calendar, 'PEAR_Error')) { $notification->push($calendar, 'horde.error'); header('Location: ' . Horde::applicationUrl('calendars/', true)); exit; -} elseif ($calendar->get('owner') != Horde_Auth::getAuth()) { - $notification->push(_("You are not allowed to delete this calendar."), 'horde.error'); - header('Location: ' . Horde::applicationUrl('calendars/', true)); - exit; } - $form = new Kronolith_DeleteCalendarForm($vars, $calendar); // Execute if the form is valid (must pass with POST variables only). diff --git a/kronolith/calendars/edit.php b/kronolith/calendars/edit.php index 1cad577b9..10faa3e55 100644 --- a/kronolith/calendars/edit.php +++ b/kronolith/calendars/edit.php @@ -23,10 +23,6 @@ if (is_a($calendar, 'PEAR_Error')) { $notification->push($calendar, 'horde.error'); header('Location: ' . Horde::applicationUrl('calendars/', true)); exit; -} elseif ($calendar->get('owner') != Horde_Auth::getAuth()) { - $notification->push(_("You are not allowed to change this calendar."), 'horde.error'); - header('Location: ' . Horde::applicationUrl('calendars/', true)); - exit; } $form = new Kronolith_EditCalendarForm($vars, $calendar); diff --git a/kronolith/calendars/remote_edit.php b/kronolith/calendars/remote_edit.php index 190326030..90f29bc45 100644 --- a/kronolith/calendars/remote_edit.php +++ b/kronolith/calendars/remote_edit.php @@ -60,6 +60,8 @@ if ($key) { $vars->set('name', $calendar['name']); $vars->set('url', $calendar['url']); +$vars->set('decription', $calendar['desc']); +$vars->set('color', $calendar['color']); $vars->set('username', $username); $vars->set('password', $password); $title = $form->getTitle(); diff --git a/kronolith/calendars/remote_unsubscribe.php b/kronolith/calendars/remote_unsubscribe.php index 5de969330..b46270fde 100644 --- a/kronolith/calendars/remote_unsubscribe.php +++ b/kronolith/calendars/remote_unsubscribe.php @@ -19,22 +19,6 @@ if (!Horde_Auth::getAuth() || $prefs->isLocked('remote_cals')) { } $vars = Horde_Variables::getDefaultVariables(); -$url = $vars->get('url'); - -$remote_calendar = null; -$remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); -foreach ($remote_calendars as $key => $calendar) { - if ($calendar['url'] == $url) { - $remote_calendar = $calendar; - break; - } -} -if (is_null($remote_calendar)) { - $notification->push(_("The remote calendar was not found."), 'horde.error'); - header('Location: ' . Horde::applicationUrl('calendars/', true)); - exit; -} - $form = new Kronolith_UnsubscribeRemoteCalendarForm($vars, $remote_calendar); // Execute if the form is valid (must pass with POST variables only). diff --git a/kronolith/js/kronolith.js b/kronolith/js/kronolith.js index 195ba7af0..70d69cbb4 100644 --- a/kronolith/js/kronolith.js +++ b/kronolith/js/kronolith.js @@ -1,6 +1,5 @@ /** * kronolith.js - Base application logic. - * NOTE: ContextSensitive.js must be loaded before this file. * * Copyright 2008-2009 The Horde Project (http://www.horde.org/) * @@ -405,6 +404,16 @@ KronolithCore = { this._addHistory(fullloc); break; + case 'calendar': + if (!this.view) { + this.go(Kronolith.conf.login_view); + this.go.bind(this, fullloc, data).defer(); + return; + } + this.editCalendar(locParts.join(':')); + this._addHistory(fullloc); + break; + case 'options': this.closeView('iframe'); this.iframeContent(loc, Kronolith.conf.prefs_url); @@ -799,6 +808,30 @@ KronolithCore = { }, /** + * Inserts a calendar entry in the sidebar menu. + * + * @param string type The calendar type. + * @param string id The calendar id. + * @param object cal The calendar object. + * @param Element div Container DIV where to add the entry (optional). + */ + insertCalendarInList: function(type, id, cal, div) + { + if (!div) { + div = this.getCalendarList(type, cal.owner); + } + if (cal.owner) { + div.insert(new Element('SPAN', { 'class': 'kronolithCalEdit' }) + .insert('›')); + } + div.insert(new Element('DIV', { 'class': cal.show ? 'kronolithCalOn' : 'kronolithCalOff' }) + .store('calendar', id) + .store('calendarclass', type) + .setStyle({ backgroundColor: cal.bg, color: cal.fg }) + .update(cal.name.escapeHTML())); + }, + + /** * Rebuilds the list of calendars. */ updateCalendarList: function() @@ -809,19 +842,11 @@ KronolithCore = { $H(Kronolith.conf.calendars.internal).each(function(cal) { if (cal.value.owner) { my++; - div = $('kronolithMyCalendars'); - div.insert(new Element('SPAN', { 'class': 'kronolithCalEdit' }) - .insert('›')); } else { shared++; - div = $('kronolithSharedCalendars'); } - div.insert(new Element('DIV', { 'class': cal.value.show ? 'kronolithCalOn' : 'kronolithCalOff' }) - .store('calendar', cal.key) - .store('calendarclass', 'internal') - .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg }) - .update(cal.value.name.escapeHTML())); - }); + this.insertCalendarInList('internal', cal.key, cal.value); + }, this); if (my) { $('kronolithMyCalendars').show(); } else { @@ -838,19 +863,11 @@ KronolithCore = { $H(Kronolith.conf.calendars.tasklists).each(function(cal) { if (cal.value.owner) { my++; - div = $('kronolithMyTasklists'); - div.insert(new Element('SPAN', { 'class': 'kronolithCalEdit' }) - .insert('›')); } else { shared++; - div = $('kronolithSharedTasklists'); } - div.insert(new Element('DIV', { 'class': cal.value.show ? 'kronolithCalOn' : 'kronolithCalOff' }) - .store('calendar', cal.key) - .store('calendarclass', 'tasklists') - .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg }) - .update(cal.value.name.escapeHTML())); - }); + this.insertCalendarInList('tasklists', cal.key, cal.value); + }, this); if (my) { $('kronolithMyTasklists').show(); } else { @@ -873,29 +890,17 @@ KronolithCore = { ext.each(function(api) { $('kronolithExternalCalendars') .insert(new Element('H3') - .insert(new Element('A', { 'class': 'kronolithAdd' }) - .update('+')) .insert({ bottom: extNames.get(api.key).escapeHTML() })) .insert(new Element('DIV', { 'id': 'kronolithExternalCalendar' + api.key, 'class': 'kronolithCalendars' })); api.value.each(function(cal) { - $('kronolithExternalCalendar' + api.key) - .insert(new Element('DIV', { 'class': cal.value.show ? 'kronolithCalOn' : 'kronolithCalOff' }) - .store('calendar', api.key + '/' + cal.key) - .store('calendarclass', 'external') - .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg }) - .update(cal.value.name.escapeHTML())); - }); - }); + this.insertCalendarInList('external', api.key + '/' + cal.key, cal.value, $('kronolithExternalCalendar' + api.key)); + }, this); + }, this); remote = $H(Kronolith.conf.calendars.remote); remote.each(function(cal) { - $('kronolithRemoteCalendars') - .insert(new Element('DIV', { 'class': cal.value.show ? 'kronolithCalOn' : 'kronolithCalOff' }) - .store('calendar', cal.key) - .store('calendarclass', 'remote') - .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg }) - .update(cal.value.name.escapeHTML())); - }); + this.insertCalendarInList('remote', cal.key, cal.value); + }, this); if (remote.size()) { $('kronolithRemoteCalendars').show(); } else { @@ -904,13 +909,8 @@ KronolithCore = { holidays = $H(Kronolith.conf.calendars.holiday); holidays.each(function(cal) { - $('kronolithHolidayCalendars') - .insert(new Element('DIV', { 'class': cal.value.show ? 'kronolithCalOn' : 'kronolithCalOff' }) - .store('calendar', cal.key) - .store('calendarclass', 'holiday') - .setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg }) - .update(cal.value.name.escapeHTML())); - }); + this.insertCalendarInList('holiday', cal.key, cal.value); + }, this); if (holidays.size()) { $('kronolithHolidayCalendars').show(); } else { @@ -919,6 +919,33 @@ KronolithCore = { }, /** + * Returns the DIV container that holds all calendars of a certain type. + * + * @param string type A calendar type + * + * @return Element The container of the calendar type. + */ + getCalendarList: function(type, personal) + { + switch (type) { + case 'internal': + return personal + ? $('kronolithMyCalendars') + : $('kronolithSharedCalendars'); + case 'tasklists': + return personal + ? $('kronolithMyTasklists') + : $('kronolithSharedTasklists'); + case 'external': + return $('kronolithExternalCalendars'); + case 'remote': + return $('kronolithRemoteCalendars'); + case 'holiday': + return $('kronolithHolidayCalendars'); + } + }, + + /** * Propagates a SELECT drop down list with the editable calendars. * * @param string id The id of the SELECT element. @@ -1980,6 +2007,180 @@ KronolithCore = { }, /** + * Opens the form for editing a calendar. + * + * @param string calendar Calendar type and calendar id, separated by '|'. + */ + editCalendar: function(calendar) + { + if ($('kronolithCalendarDialog')) { + RedBox.showHtml($('kronolithCalendarDialog').show()); + this._editCalendar(calendar); + } else { + RedBox.loading(); + this.doAction('ChunkContent', { 'chunk': 'calendar' }, function(r) { + if (r.response.chunk) { + RedBox.showHtml(r.response.chunk); + this._editCalendar(calendar); + } + }.bind(this)); + } + }, + + /** + * Callback for editing a calendar. Fills the edit form with the correct + * values. + * + * @param string calendar Calendar type and calendar id, separated by '|'. + */ + _editCalendar: function(calendar) + { + calendar = calendar.split('|'); + var type = calendar[0]; + calendar = calendar.length == 1 ? null : calendar[1]; + + $('kronolithCalendarDialog').select('.kronolithCalendarDiv').invoke('hide'); + $('kronolithCalendar' + type + '1').show(); + $('kronolithCalendarForm' + type).select('.kronolithCalendarContinue').invoke('enable'); + + /* Reset form to defaults if this is for adding calendars. */ + if (!calendar) { + var fields = [ 'Id', 'Name' ]; + switch (type) { + case 'internal': + case 'tasklists': + fields.push('Description'); + break; + case 'remote': + fields.push('Description', 'Url', 'Username', 'Password'); + break; + } + fields.each(function(field) { + $('kronolithCalendar' + type + field).clear(); + }); + $('kronolithCalendar' + type + 'Color').setValue('#dddddd').setStyle({ 'backgroundColor': '#dddddd', 'color': '#000' }); + $('kronolithCalendarForm' + type).down('.kronolithCalendarDelete').hide(); + return; + } + + if (Object.isUndefined(Kronolith.conf.calendars[type]) || + Object.isUndefined(Kronolith.conf.calendars[type][calendar])) { + this._closeRedBox(); + window.history.back(); + return; + } + + var info = Kronolith.conf.calendars[type][calendar]; + + $('kronolithCalendar' + type + 'Id').setValue(calendar); + $('kronolithCalendar' + type + 'Name').setValue(info.name); + $('kronolithCalendar' + type + 'Color').setValue(info.bg).setStyle({ 'backgroundColor': info.bg, 'color': info.fg }); + + switch (type) { + case 'internal': + case 'tasklists': + $('kronolithCalendarinternalDescription').setValue(info.desc); + break; + case 'remote': + $('kronolithCalendarremoteUrl').setValue(calendar); + $('kronolithCalendarremoteDescription').setValue(info.desc); + $('kronolithCalendarremoteUsername').setValue(info.user); + $('kronolithCalendarremotePassword').setValue(info.password); + break; + } + + /* Currently we only show the calendar form for own calendars anyway, + but be prepared. */ + var form = $('kronolithCalendarForm' + type); + if (info.owner) { + form.enable(); + if (type == 'internal' && + calendar == Kronolith.conf.user) { + form.down('.kronolithCalendarDelete').hide(); + } else { + form.down('.kronolithCalendarDelete').show(); + } + form.down('.kronolithCalendarSave').show(); + } else { + form.disable(); + form.down('.kronolithCalendarDelete').hide(); + form.down('.kronolithCalendarSave').hide(); + } + }, + + /** + * Opens the next screen of the calendar management wizard. + * + * @param string type The calendar type. + */ + _calendarNext: function(type) + { + var i = 1; + while (!$('kronolithCalendar' + type + i).visible()) { + i++; + } + $('kronolithCalendar' + type + i).hide(); + $('kronolithCalendar' + type + ++i).show(); + }, + + /** + * Submits the calendar form to save the calendar data. + * + * @param Element The form node. + */ + saveCalendar: function(form) + { + var type = form.id.replace(/kronolithCalendarForm/, ''), + data = form.serialize({ 'hash': true }); + + this.doAction('SaveCalendar', + data, + function(r) { + if (r.response.saved) { + if (data.calendar) { + var cal = Kronolith.conf.calendars[type][data.calendar], + color = { + backgroundColor: data.color, + color: r.response.color + }; + cal.bg = data.color; + cal.fg = r.response.color; + cal.name = data.name; + cal.desc = data.description; + this.getCalendarList(type, cal.owner).select('div').each(function(element) { + if (element.retrieve('calendar') == data.calendar) { + element + .setStyle(color) + .update(cal.name.escapeHTML()); + throw $break; + } + }); + $('kronolithBody').select('div').each(function(el) { + if (el.retrieve('calendar') == type + '|' + data.calendar) { + el.setStyle(color); + } + }); + } else { + var cal = { + bg: data.color, + fg: r.response.color, + name: data.name, + desc: data.description, + edit: true, + owner: true, + show: true + }; + Kronolith.conf.calendars[type][r.response.calendar] = cal; + this.insertCalendarInList(type, r.response.calendar, cal); + } + } + form.down('.kronolithCalendarSave').enable(); + this._closeRedBox(); + window.history.back(); + }.bind(this)); + }, + + /** * Parses a date attribute string into a Date object. * * For other strings use Date.parse(). @@ -2144,9 +2345,9 @@ KronolithCore = { }, /** - * Deletes an event from the cache. + * Deletes an event or a complete calendar from the cache. * - * @param string event An event ID. + * @param string event An event ID or empty if deleting the calendar. * @param string calendar A calendar string or array. */ _deleteCache: function(event, calendar) @@ -2158,9 +2359,13 @@ KronolithCore = { !this.ecache.get(calendar[0]).get(calendar[1])) { return; } - this.ecache.get(calendar[0]).get(calendar[1]).each(function(day) { - day.value.unset(event); - }); + if (event) { + this.ecache.get(calendar[0]).get(calendar[1]).each(function(day) { + day.value.unset(event); + }); + } else { + this.ecache.get(calendar[0]).unset(calendar[1]); + } }, /** @@ -2271,6 +2476,14 @@ KronolithCore = { this.go('search:' + $F('kronolithSearchTerm')) e.stop(); break; + + case 'kronolithCalendarForminternal': + case 'kronolithCalendarFormtasklists': + case 'kronolithCalendarFormremote': + // Disabled for now, we have to also catch Continue buttons. + //this.saveCalendar(form); + //e.stop(); + break; } break; @@ -2355,29 +2568,6 @@ KronolithCore = { this.toggleAllDay(); return; - case 'kronolithEventLinkDescription': - case 'kronolithEventLinkReminder': - case 'kronolithEventLinkRecur': - case 'kronolithEventLinkUrl': - case 'kronolithEventLinkAttendees': - case 'kronolithEventLinkTags': - $('kronolithEventDialog').select('.kronolithTabsOption').invoke('hide'); - $(id.replace(/Link/, 'Tab')).show(); - $('kronolithEventDialog').select('.tabset li').invoke('removeClassName', 'activeTab'); - elt.parentNode.addClassName('activeTab'); - e.stop(); - return; - - case 'kronolithTaskLinkDescription': - case 'kronolithTaskLinkReminder': - case 'kronolithTaskLinkUrl': - $('kronolithTaskDialog').select('.kronolithTabsOption').invoke('hide'); - $(id.replace(/Link/, 'Tab')).show(); - $('kronolithTaskDialog').select('.tabset li').invoke('removeClassName', 'activeTab'); - elt.parentNode.addClassName('activeTab'); - e.stop(); - return; - case 'kronolithEventLinkNone': case 'kronolithEventLinkDaily': case 'kronolithEventLinkWeekly': @@ -2389,11 +2579,13 @@ KronolithCore = { case 'kronolithEventSave': this.saveEvent(); + elt.disable(); e.stop(); return; case 'kronolithTaskSave': this.saveTask(); + elt.disable(); e.stop(); return; @@ -2445,13 +2637,6 @@ KronolithCore = { e.stop(); return; - case 'kronolithEventCancel': - case 'kronolithTaskCancel': - this._closeRedBox(); - window.history.back(); - e.stop(); - return; - case 'kronolithNavDay': case 'kronolithNavWeek': case 'kronolithNavMonth': @@ -2603,6 +2788,26 @@ KronolithCore = { e.stop(); return; + case 'kronolithAdd': + this.go('calendar:' + id.replace(/kronolithAdd/, '')); + e.stop(); + return; + + case 'kronolithTabLink': + var dialog = elt.up('form'); + dialog.select('.kronolithTabsOption').invoke('hide'); + dialog.select('.tabset li').invoke('removeClassName', 'activeTab'); + $(id.replace(/Link/, 'Tab')).show(); + elt.parentNode.addClassName('activeTab'); + e.stop(); + return; + + case 'kronolithFormCancel': + this._closeRedBox(); + window.history.back(); + e.stop(); + return; + case 'kronolithEventTag': $('kronolithEventTags').autocompleter.addNewItemNode(elt.getText()); e.stop(); @@ -2616,6 +2821,11 @@ KronolithCore = { } e.stop(); return; + + case 'kronolithCalEdit': + this.go('calendar:' + elt.next().retrieve('calendarclass') + '|' + elt.next().retrieve('calendar')); + e.stop(); + return; } if (elt.hasClassName('kronolithEvent')) { @@ -2657,6 +2867,103 @@ KronolithCore = { }.bind(this)); e.stop(); return; + } else if (elt.hasClassName('kronolithCalendarSave')) { + elt.disable(); + this.saveCalendar(elt.up('form')); + e.stop(); + return; + } else if (elt.hasClassName('kronolithCalendarContinue')) { + var form = elt.up('form'), + type = form.id.replace(/kronolithCalendarForm/, ''), + i = 1; + while (!$('kronolithCalendar' + type + i).visible()) { + i++; + } + if (type == 'remote' && !$F('kronolithCalendarremoteId')) { + elt.disable(); + if (i == 1) { + if (!$F('kronolithCalendarremoteUrl')) { + this.showNotifications([ { type: 'horde.warning', message: Kronolith.text.no_url }]); + e.stop(); + return; + } + this.doAction('GetRemoteInfo', + { url: $F('kronolithCalendarremoteUrl') }, + function(r) { + if (r.response.success) { + if (r.response.name) { + $('kronolithCalendarremoteName').setValue(r.response.name); + } + if (r.response.desc) { + $('kronolithCalendarremoteDescription').setValue(r.response.desc); + } + this._calendarNext(type); + this._calendarNext(type); + } else if (r.response.auth) { + this._calendarNext(type); + } else { + elt.enable(); + } + }.bind(this), + { asynchronous: false }); + } + if (i == 2) { + var params = { url: $F('kronolithCalendarremoteUrl') }; + if ($F('kronolithCalendarremoteUsername')) { + params.username = $F('kronolithCalendarremoteUsername'); + params.password = $F('kronolithCalendarremotePassword'); + } + this.doAction('GetRemoteInfo', + params, + function(r) { + if (r.response.success) { + if (r.response.name) { + $('kronolithCalendarremoteName').setValue(r.response.name); + } + if (r.response.desc) { + $('kronolithCalendarremoteDescription').setValue(r.response.desc); + } + this._calendarNext(type); + } else if (r.response.auth) { + this.showNotifications([{ type: 'horde.warning', message: Kronolith.text.wrong_auth }]); + elt.enable(); + } else { + elt.enable(); + } + }.bind(this)); + } + e.stop(); + return; + } + this._calendarNext(type); + elt.disable(); + e.stop(); + return; + } else if (elt.hasClassName('kronolithCalendarDelete')) { + var form = elt.up('form'), + type = form.id.replace(/kronolithCalendarForm/, ''), + calendar = $F('kronolithCalendar' + type + 'Id'); + this.doAction('DeleteCalendar', + { 'type': type, 'calendar': calendar }, + function(r) { + if (r.response.deleted) { + var div = this.getCalendarList(type, Kronolith.conf.calendars[type][calendar].owner).select('div').find(function(element) { + return element.retrieve('calendar') == calendar; + }); + div.previous('span').remove(); + div.remove(); + this._deleteCache(null, calendar); + $('kronolithBody').select('div').findAll(function(el) { + return el.retrieve('calendar') == calendar; + }).invoke('remove'); + delete Kronolith.conf.calendars[type][calendar]; + } + this._closeRedBox(); + window.history.back(); + }.bind(this)); + elt.disable(); + e.stop(); + return; } calClass = elt.retrieve('calendarclass'); @@ -3069,7 +3376,10 @@ KronolithCore = { _closeRedBox: function() { - document.body.insert(RedBox.getWindowContents().hide()); + var content = RedBox.getWindowContents(); + if (content) { + document.body.insert(content.hide()); + } RedBox.close(); }, diff --git a/kronolith/lib/Driver/Ical.php b/kronolith/lib/Driver/Ical.php index 19df1e191..36b24bcc2 100644 --- a/kronolith/lib/Driver/Ical.php +++ b/kronolith/lib/Driver/Ical.php @@ -55,7 +55,7 @@ class Kronolith_Driver_Ical extends Kronolith_Driver $showRecurrence = false, $hasAlarm = false, $json = false) { - $iCal = $this->_getRemoteCalendar(); + $iCal = $this->getRemoteCalendar(); if (is_a($iCal, 'PEAR_Error')) { return $iCal; } @@ -141,7 +141,7 @@ class Kronolith_Driver_Ical extends Kronolith_Driver return new Kronolith_Event_Ical($this); } $eventId = str_replace('ical', '', $eventId); - $iCal = $this->_getRemoteCalendar(); + $iCal = $this->getRemoteCalendar(); if (is_a($iCal, 'PEAR_Error')) { return $iCal; } @@ -164,9 +164,11 @@ class Kronolith_Driver_Ical extends Kronolith_Driver /** * Fetches a remote calendar into the session and return the data. * + * @param boolean $cache Whether to return data from the session cache. + * * @return Horde_iCalendar The calendar data, or an error on failure. */ - private function _getRemoteCalendar() + public function getRemoteCalendar($cache = true) { $url = trim($this->_calendar); @@ -175,7 +177,7 @@ class Kronolith_Driver_Ical extends Kronolith_Driver $url = str_replace('webcal://', 'http://', $url); } - if (!empty($_SESSION['kronolith']['remote'][$url])) { + if ($cache && !empty($_SESSION['kronolith']['remote'][$url])) { return $_SESSION['kronolith']['remote'][$url]; } @@ -206,7 +208,7 @@ class Kronolith_Driver_Ical extends Kronolith_Driver Horde::logMessage(sprintf('Failed to retrieve remote calendar: url = "%s", status = %s', $url, $http->getResponseCode()), __FILE__, __LINE__, PEAR_LOG_INFO); - $_SESSION['kronolith']['remote'][$url] = PEAR::raiseError(sprintf(_("Could not open %s."), $url)); + $_SESSION['kronolith']['remote'][$url] = PEAR::raiseError(sprintf(_("Could not open %s."), $url), $http->getResponseCode()); return $_SESSION['kronolith']['remote'][$url]; } diff --git a/kronolith/lib/Event/Horde.php b/kronolith/lib/Event/Horde.php index 4e308b0c8..c3d850d45 100644 --- a/kronolith/lib/Event/Horde.php +++ b/kronolith/lib/Event/Horde.php @@ -61,6 +61,11 @@ class Kronolith_Event_Horde extends Kronolith_Event $this->end = $eventEnd; $this->status = Kronolith::STATUS_FREE; + if (isset($event['color'])) { + $this->_backgroundColor = $event['color']; + $this->_foregroundColor = Horde_Image::brightness($this->_backgroundColor) < 128 ? '#fff' : '#000'; + } + if (isset($event['recurrence'])) { $recurrence = new Horde_Date_Recurrence($eventStart); diff --git a/kronolith/lib/Forms/CreateCalendar.php b/kronolith/lib/Forms/CreateCalendar.php index 87c3524d8..7a71c5220 100755 --- a/kronolith/lib/Forms/CreateCalendar.php +++ b/kronolith/lib/Forms/CreateCalendar.php @@ -31,23 +31,17 @@ class Kronolith_CreateCalendarForm extends Horde_Form { $this->addVariable(_("Color"), 'color', 'colorpicker', false); $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60)); $this->addVariable(_("Tags"), 'tags', 'text', false); + $this->setButtons(array(_("Create"))); } function execute() { - // Create new share. - $calendar = $GLOBALS['kronolith_shares']->newShare(hash('md5', microtime())); - if (is_a($calendar, 'PEAR_Error')) { - return $calendar; + $info = array(); + foreach (array('name', 'color', 'description', 'tags') as $key) { + $info[$key] = $this->_vars->get($key); } - $calendar->set('name', $this->_vars->get('name')); - $calendar->set('color', $this->_vars->get('color')); - $calendar->set('desc', $this->_vars->get('description')); - $tagger = Kronolith::getTagger(); - - $tagger->tag($calendar->getName(), $this->_vars->get('tags'), 'calendar'); - return $GLOBALS['kronolith_shares']->addShare($calendar); + return Kronolith::addShare($info); } } diff --git a/kronolith/lib/Forms/DeleteCalendar.php b/kronolith/lib/Forms/DeleteCalendar.php index 4d751d480..0791b027a 100644 --- a/kronolith/lib/Forms/DeleteCalendar.php +++ b/kronolith/lib/Forms/DeleteCalendar.php @@ -46,42 +46,7 @@ class Kronolith_DeleteCalendarForm extends Horde_Form { return false; } - if ($this->_calendar->get('owner') != Horde_Auth::getAuth()) { - return PEAR::raiseError(_("Permission denied")); - } - - // Delete the calendar. - $result = Kronolith::getDriver()->delete($this->_calendar->getName()); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError(sprintf(_("Unable to delete \"%s\": %s"), $this->_calendar->get('name'), $result->getMessage())); - } else { - // Remove share and all groups/permissions. - $result = $GLOBALS['kronolith_shares']->removeShare($this->_calendar); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - } - - // Make sure we still own at least one calendar. - if (count(Kronolith::listCalendars(true)) == 0) { - // If the default share doesn't exist then create it. - if (!$GLOBALS['kronolith_shares']->exists(Horde_Auth::getAuth())) { - $identity = Horde_Prefs_Identity::singleton(); - $name = $identity->getValue('fullname'); - if (trim($name) == '') { - $name = Horde_Auth::getOriginalAuth(); - } - $calendar = &$GLOBALS['kronolith_shares']->newShare(Horde_Auth::getAuth()); - if (is_a($calendar, 'PEAR_Error')) { - return; - } - $calendar->set('name', sprintf(_("%s's Calendar"), $name)); - $GLOBALS['kronolith_shares']->addShare($calendar); - $GLOBALS['all_calendars'][Auth::getAuth()] = &$calendar; - } - } - - return true; + return Kronolith::deleteShare($this->_calendar); } } diff --git a/kronolith/lib/Forms/EditCalendar.php b/kronolith/lib/Forms/EditCalendar.php index d7cafdc5c..a4673b24e 100644 --- a/kronolith/lib/Forms/EditCalendar.php +++ b/kronolith/lib/Forms/EditCalendar.php @@ -43,26 +43,11 @@ class Kronolith_EditCalendarForm extends Horde_Form { function execute() { - $original_name = $this->_calendar->get('name'); - $new_name = $this->_vars->get('name'); - $this->_calendar->set('name', $new_name); - $this->_calendar->set('color', $this->_vars->get('color')); - $this->_calendar->set('desc', $this->_vars->get('description')); - if ($original_name != $new_name) { - $result = Kronolith::getDriver()->rename($original_name, $new_name); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError(sprintf(_("Unable to rename \"%s\": %s"), $original_name, $result->getMessage())); - } + $info = array(); + foreach (array('name', 'color', 'description', 'tags') as $key) { + $info[$key] = $this->_vars->get($key); } - - $result = $this->_calendar->save(); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError(sprintf(_("Unable to save calendar \"%s\": %s"), $new_name, $result->getMessage())); - } - - $tagger = Kronolith::getTagger(); - $tagger->replaceTags($this->_calendar->getName(), $this->_vars->get('tags'), 'calendar'); - return true; + return Kronolith::updateShare($this->_calendar, $info); } } diff --git a/kronolith/lib/Forms/SubscribeRemoteCalendar.php b/kronolith/lib/Forms/SubscribeRemoteCalendar.php index cb7020eba..e04c5655d 100644 --- a/kronolith/lib/Forms/SubscribeRemoteCalendar.php +++ b/kronolith/lib/Forms/SubscribeRemoteCalendar.php @@ -28,7 +28,9 @@ class Kronolith_SubscribeRemoteCalendarForm extends Horde_Form { parent::Horde_Form($vars, _("Subscribe to a Remote Calendar")); $this->addVariable(_("Name"), 'name', 'text', true); + $this->addVariable(_("Color"), 'color', 'colorpicker', false); $this->addVariable(_("URL"), 'url', 'text', true); + $this->addVariable(_("Description"), 'description', 'longtext', false, false, null, array(4, 60)); $this->addVariable(_("Username"), 'username', 'text', false); $this->addVariable(_("Password"), 'password', 'password', false); @@ -37,33 +39,11 @@ class Kronolith_SubscribeRemoteCalendarForm extends Horde_Form { function execute() { - $name = trim($this->_vars->get('name')); - $url = trim($this->_vars->get('url')); - $username = trim($this->_vars->get('username')); - $password = trim($this->_vars->get('password')); - - if (!(strlen($name) && strlen($url))) { - return false; - } - - if (strlen($username) || strlen($password)) { - $key = Horde_Auth::getCredential('password'); - if ($key) { - $username = base64_encode(Horde_Secret::write($key, $username)); - $password = base64_encode(Horde_Secret::write($key, $password)); - } + $info = array(); + foreach (array('name', 'url', 'color', 'username', 'password') as $key) { + $info[$key] = $this->_vars->get($key); } - - $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); - $remote_calendars[] = array( - 'name' => $name, - 'url' => $url, - 'user' => $username, - 'password' => $password, - ); - - $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); - return true; + return Kronolith::subscribeRemoteCalendar($info); } } diff --git a/kronolith/lib/Forms/UnsubscribeRemoteCalendar.php b/kronolith/lib/Forms/UnsubscribeRemoteCalendar.php index 7e99042b2..8dc48785e 100644 --- a/kronolith/lib/Forms/UnsubscribeRemoteCalendar.php +++ b/kronolith/lib/Forms/UnsubscribeRemoteCalendar.php @@ -40,21 +40,7 @@ class Kronolith_UnsubscribeRemoteCalendarForm extends Horde_Form { return false; } - $url = trim($this->_vars->get('url')); - if (!strlen($url)) { - return false; - } - - $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); - foreach ($remote_calendars as $key => $calendar) { - if ($calendar['url'] == $url) { - unset($remote_calendars[$key]); - break; - } - } - - $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); - return true; + return Kronolit::unsubscribeRemoteCalendar($this->_vars->get('url')); } } diff --git a/kronolith/lib/Kronolith.php b/kronolith/lib/Kronolith.php index 6c184748c..5a3583551 100644 --- a/kronolith/lib/Kronolith.php +++ b/kronolith/lib/Kronolith.php @@ -126,6 +126,7 @@ class Kronolith 'URI_IMG' => $registry->getImageDir() . '/', 'URI_SNOOZE' => Horde::url($registry->get('webroot', 'horde') . '/services/snooze.php', true, -1), 'SESSION_ID' => defined('SID') ? SID : '', + 'user' => Horde_Auth::getAuth(), 'prefs_url' => str_replace('&', '&', Horde::getServiceLink('options', 'kronolith')), 'name' => $registry->get('name'), 'is_ie6' => ($browser->isBrowser('msie') && ($browser->getMajor() < 7)), @@ -169,6 +170,7 @@ class Kronolith $code['conf']['calendars']['internal'][$id] = array( 'name' => ($owner ? '' : '[' . Horde_Auth::convertUsername($calendar->get('owner'), false) . '] ') . $calendar->get('name'), + 'desc' => $calendar->get('desc'), 'owner' => $owner, 'fg' => self::foregroundColor($calendar), 'bg' => self::backgroundColor($calendar), @@ -187,6 +189,7 @@ class Kronolith $code['conf']['calendars']['tasklists']['tasks/' . $id] = array( 'name' => ($owner ? '' : '[' . Horde_Auth::convertUsername($tasklist->get('owner'), false) . '] ') . $tasklist->get('name'), + 'desc' => $tasklist->get('desc'), 'owner' => $owner, 'fg' => self::foregroundColor($tasklist), 'bg' => self::backgroundColor($tasklist), @@ -214,11 +217,15 @@ class Kronolith // Remote calendars foreach ($GLOBALS['all_remote_calendars'] as $calendar) { - $code['conf']['calendars']['remote'][$calendar['url']] = array( - 'name' => $calendar['name'], - 'fg' => self::foregroundColor($calendar), - 'bg' => self::backgroundColor($calendar), - 'show' => in_array($calendar['url'], $GLOBALS['display_remote_calendars'])); + + $code['conf']['calendars']['remote'][$calendar['url']] = array_merge( + array('name' => $calendar['name'], + 'desc' => isset($calendar['desc']) ? $calendar['desc'] : '', + 'owner' => true, + 'fg' => self::foregroundColor($calendar), + 'bg' => self::backgroundColor($calendar), + 'show' => in_array($calendar['url'], $GLOBALS['display_remote_calendars'])), + self::getRemoteParams($calendar['url'])); } // Holidays @@ -243,6 +250,8 @@ class Kronolith 'searching' => str_replace('%s', '#{term}', _("Events matching \"%s\"")), 'allday' => _("All day"), 'prefs' => _("Options"), + 'no_url' => _("You must specify a URL."), + 'wrong_auth' => _("The authentication information you specified wasn't accepted."), ); for ($i = 1; $i <= 12; ++$i) { $code['text']['month'][$i - 1] = Horde_Nls::getLangInfo(constant('MON_' . $i)); @@ -1290,6 +1299,158 @@ class Kronolith } /** + * Creates a new share. + * + * @param array $info Hash with calendar information. + * + * @return Horde_Share The new share. + */ + public static function addShare($info) + { + $calendar = $GLOBALS['kronolith_shares']->newShare(hash('md5', microtime())); + if (is_a($calendar, 'PEAR_Error')) { + return $calendar; + } + + $calendar->set('name', $info['name']); + $calendar->set('color', $info['color']); + $calendar->set('desc', $info['description']); + $tagger = self::getTagger(); + $tagger->tag($calendar->getName(), $info['tags'], 'calendar'); + + $result = $GLOBALS['kronolith_shares']->addShare($calendar); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + $GLOBALS['display_calendars'][] = $calendar->getName(); + $GLOBALS['prefs']->setValue('display_cals', serialize($GLOBALS['display_calendars'])); + + return $calendar; + } + + /** + * Updates an existing share. + * + * @param Horde_Share $share The share to update. + * @param array $info Hash with calendar information. + */ + public static function updateShare(&$calendar, $info) + { + if ($calendar->get('owner') != Horde_Auth::getAuth()) { + return PEAR::raiseError(_("You are not allowed to change this calendar.")); + } + + $original_name = $calendar->get('name'); + $calendar->set('name', $info['name']); + $calendar->set('color', $info['color']); + $calendar->set('desc', $info['description']); + if ($original_name != $info['name']) { + $result = Kronolith::getDriver()->rename($original_name, $info['name']); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError(sprintf(_("Unable to rename \"%s\": %s"), $original_name, $result->getMessage())); + } + } + + $result = $calendar->save(); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError(sprintf(_("Unable to save calendar \"%s\": %s"), $info['name'], $result->getMessage())); + } + + $tagger = self::getTagger(); + $tagger->replaceTags($calendar->getName(), $info['tags'], 'calendar'); + } + + /** + * Deletes a share. + * + * @param Horde_Share $calendar The share to delete. + */ + public static function deleteShare($calendar) + { + if ($calendar->getName() == Horde_Auth::getAuth()) { + return PEAR::raiseError(_("This calendar cannot be deleted.")); + } + + if ($calendar->get('owner') != Horde_Auth::getAuth()) { + return PEAR::raiseError(_("You are not allowed to delete this calendar.")); + } + + // Delete the calendar. + $result = Kronolith::getDriver()->delete($calendar->getName()); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $result->getMessage())); + } + + // Remove share and all groups/permissions. + return $GLOBALS['kronolith_shares']->removeShare($calendar); + } + + /** + * Subscribes to a remote calendar. + * + * @param array $info Hash with calendar information. + */ + public static function subscribeRemoteCalendar($info) + { + if (!(strlen($info['name']) && strlen($info['url']))) { + return PEAR::raiseError(_("You must specify a name and a URL.")); + } + + if (strlen($info['username']) || strlen($info['password'])) { + $key = Horde_Auth::getCredential('password'); + if ($key) { + $info['username'] = base64_encode(Horde_Secret::write($key, $info['username'])); + $info['password'] = base64_encode(Horde_Secret::write($key, $info['password'])); + } + } + + $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); + $remote_calendars[] = array( + 'name' => $info['name'], + 'desc' => $info['description'], + 'url' => $info['url'], + 'color' => $info['color'], + 'user' => $info['username'], + 'password' => $info['password'], + ); + + $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); + } + + /** + * Unsubscribes from a remote calendar. + * + * @param string $url The calendar URL. + * + * @return array Hash with the deleted calendar's information. + */ + public static function unsubscribeRemoteCalendar($url) + { + $url = trim($url); + if (!strlen($url)) { + return false; + } + + $remote_calendars = unserialize($GLOBALS['prefs']->getValue('remote_cals')); + $remote_calendar = null; + foreach ($remote_calendars as $key => $calendar) { + if ($calendar['url'] == $url) { + $remote_calendar = $calendar; + unset($remote_calendars[$key]); + break; + } + } + if (!$remote_calendar) { + return PEAR::raiseError(_("The remote calendar was not found.")); + } + + $GLOBALS['prefs']->setValue('remote_cals', serialize($remote_calendars)); + + return $remote_calendar; + } + + /** * Returns the feed URL for a calendar. * * @param string $calendar A calendar name. @@ -1918,11 +2079,11 @@ class Kronolith break; case 'Ical': + $params = self::getRemoteParams($calendar); /* Check for HTTP proxy configuration */ if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) { $params['proxy'] = $GLOBALS['conf']['http']['proxy']; } - $params = self::getRemoteParams($calendar); break; case 'Horde': @@ -1969,6 +2130,7 @@ class Kronolith if (!empty($user)) { return array('user' => $user, 'password' => $password); } + return array(); } } diff --git a/kronolith/templates/chunks/calendar.php b/kronolith/templates/chunks/calendar.php new file mode 100644 index 000000000..df4c291a6 --- /dev/null +++ b/kronolith/templates/chunks/calendar.php @@ -0,0 +1,161 @@ +
+ +
+ + + +
+
+ +
+ +
+ +
+ +
+
    +
  • +
  • +
  • +
+
+
+ +
+ +
+ + + + + +
+ " class="kronolithCalendarSave button ok" /> + " class="kronolithCalendarDelete button ko" /> + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ +
+ +
+
    +
  • +
  • +
+
+
+ +
+ +
+ + + +
+ " class="kronolithCalendarSave button ok" /> + " class="kronolithCalendarDelete button ko" /> + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ +
+ +
+ " class="kronolithCalendarContinue button ok" /> + " class="kronolithCalendarDelete button ko" /> + +
+
+ +
+
+ +
+ +
+ +
+ +
+ +
+ " class="kronolithCalendarContinue button ok" /> + +
+
+ +
+
+ +
+ +
+ +
+ +
+ " class="kronolithCalendarSave button ok" /> + " class="kronolithCalendarDelete button ko" /> + +
+
+ +
+ +
diff --git a/kronolith/templates/index/edit.inc b/kronolith/templates/index/edit.inc index c8dd03868..9ce11f756 100644 --- a/kronolith/templates/index/edit.inc +++ b/kronolith/templates/index/edit.inc @@ -1,4 +1,4 @@ -