diff --git a/ChangeLog b/ChangeLog index 7a9b6e429..8b4beaaff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +2010-08-25 Francis Lachapelle + + * UI/WebServerResources/SchedulerUI.js (copyEventToClipboard) + (copyEventFromClipboard, copyEventToPersonalCalendar, copyEvents): + new functions to allow the copy/paste of events. + (selectCalendarEvent): fixed various bugs with the selection of + events. + (onMenuCurrentViewPrepareVisibility): new function to disable the + delete option when no event is selected and to disable the copy + option when the event is owned by the user. + (onDocumentKeydown): added actions for Ctrl-C and Ctrl-V. + (deleteEvent): avoid showing a confirmation dialog with recurrent + events that the user can't erase. + + * UI/WebServerResources/SOGoDataTable.js: Overrided methods from + HTMLElement (selectRange, selectAll) to handle selection based on rows ID. + + + * UI/WebServerResources/MailerUI.js: selected messages are now + identified by their row ID to handle live loading. + + * UI/WebServerResources/HTMLElement.js (getSelectedNodesId): take + advantage of the selectedIds attribute. + (selectElement): also verify if the element is in the selectedIds + array. + (deselectAll): performance improvement; unselect elements based on + the _selected class. + + * UI/Scheduler/UIxAppointmentActions.m (-copyAction): new method + to copy the appointment to a different calendar. + + * SoObjects/Appointments/SOGoCalendarComponent.m + (-copyComponent:toFolder:): was copyToFolder. This new method + accepts a specific calendar component. + (-copyToFolder:): uses the above method with the current calendar. + 2010-08-25 Wolfgang Sourdeau * UI/Contacts/UIxListEditor.m: slightly improved code at various diff --git a/SoObjects/Appointments/SOGoCalendarComponent.h b/SoObjects/Appointments/SOGoCalendarComponent.h index 1f35fc2bc..3ad1a73c0 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.h +++ b/SoObjects/Appointments/SOGoCalendarComponent.h @@ -55,6 +55,9 @@ - (BOOL) expandGroupsInEvent: (iCalEvent *) theEvent; +- (NSException *) copyComponent: (iCalCalendar *) calendar + toFolder: (SOGoGCSFolder *) newFolder; + - (void) saveComponent: (iCalRepeatableEntityObject *) newObject; /* mail notifications */ diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 0c4f2aaf9..caac31e59 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -1,6 +1,6 @@ /* SOGoCalendarComponent.m - this file is part of SOGo * - * Copyright (C) 2006-2009 Inverse inc. + * Copyright (C) 2006-2010 Inverse inc. * * Author: Wolfgang Sourdeau * @@ -1041,16 +1041,20 @@ static inline BOOL _occurenceHasID (iCalRepeatableEntityObject *occurence, } - (NSException *) copyToFolder: (SOGoGCSFolder *) newFolder +{ + return [self copyComponent: [self calendar: NO secure: NO] + toFolder: newFolder]; +} + +- (NSException *) copyComponent: (iCalCalendar *) calendar + toFolder: (SOGoGCSFolder *) newFolder { NSArray *elements; NSString *newUID; unsigned int count, max; - iCalCalendar *calendar; SOGoCalendarComponent *newComponent; newUID = [self globallyUniqueObjectId]; - calendar = [self calendar: NO secure: NO]; - elements = [calendar allObjects]; max = [elements count]; for (count = 0; count < max; count++) diff --git a/UI/Common/BrazilianPortuguese.lproj/Localizable.strings b/UI/Common/BrazilianPortuguese.lproj/Localizable.strings index 6b73bfa2a..0def588e2 100644 --- a/UI/Common/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Common/BrazilianPortuguese.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Você não está liberado para acessar este módulo ou este sistema. Por favor, contate seu administrador de sistemas."; diff --git a/UI/Common/Czech.lproj/Localizable.strings b/UI/Common/Czech.lproj/Localizable.strings index 0db6d1f6b..7f0b6d241 100644 --- a/UI/Common/Czech.lproj/Localizable.strings +++ b/UI/Common/Czech.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Nemáte oprávnění pro přístup k tomuto modulu nebo systému. Kontaktujte prosím svého systémového administrátora."; diff --git a/UI/Common/Dutch.lproj/Localizable.strings b/UI/Common/Dutch.lproj/Localizable.strings index 536cdf0f6..5946a1039 100644 --- a/UI/Common/Dutch.lproj/Localizable.strings +++ b/UI/Common/Dutch.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "U hebt geen toegang tot deze module of dit systeem. Neem contact op met uw systeem beheerder."; diff --git a/UI/Common/English.lproj/Localizable.strings b/UI/Common/English.lproj/Localizable.strings index 9377dc92c..15cac4f48 100644 --- a/UI/Common/English.lproj/Localizable.strings +++ b/UI/Common/English.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "You are not allowed to access this module or this system. Please contact your system administrator."; diff --git a/UI/Common/French.lproj/Localizable.strings b/UI/Common/French.lproj/Localizable.strings index 24224ff43..ef98d6a9d 100644 --- a/UI/Common/French.lproj/Localizable.strings +++ b/UI/Common/French.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "Un dossier du même nom existe déjà."; "You cannot create a list in a shared address book." = "Impossible de créer une liste dans un dossier partagé."; +"Warning" = "Avertissement"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Vous n'êtes pas autorisé à accéder à ce module ou ce système. Veuillez contacter votre administrateur système."; diff --git a/UI/Common/German.lproj/Localizable.strings b/UI/Common/German.lproj/Localizable.strings index fe845a8ef..6a17ae6e1 100644 --- a/UI/Common/German.lproj/Localizable.strings +++ b/UI/Common/German.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "Ein Ordner mit diesem Namen existiert bereits."; "You cannot create a list in a shared address book." = "Es ist nicht möglich, eine Liste in einem gemeinsamen Adressbuch zu erstellen."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Sie sind nicht berechtigt auf dieses Modul oder System zuzugreifen. Bitte kontaktieren Sie ihren Systemadministrator."; diff --git a/UI/Common/Hungarian.lproj/Localizable.strings b/UI/Common/Hungarian.lproj/Localizable.strings index a9214427e..4f48b090b 100644 --- a/UI/Common/Hungarian.lproj/Localizable.strings +++ b/UI/Common/Hungarian.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Önnek nem engedélyezett a hozzáférés ehhez a modulhoz vagy rendszerhez. Kérem lépjen kapcsolatba a rendszergazdával."; diff --git a/UI/Common/Italian.lproj/Localizable.strings b/UI/Common/Italian.lproj/Localizable.strings index 9905bc230..cf6ebebac 100644 --- a/UI/Common/Italian.lproj/Localizable.strings +++ b/UI/Common/Italian.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Non sei abilitato ad accedere a questo modulo. Contatta il tuo amministratore di sistema."; diff --git a/UI/Common/Polish.lproj/Localizable.strings b/UI/Common/Polish.lproj/Localizable.strings index f80278b6d..31f8bfe33 100644 --- a/UI/Common/Polish.lproj/Localizable.strings +++ b/UI/Common/Polish.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "Folder o tej nazwie już istnieje."; "You cannot create a list in a shared address book." = "Nie możesz tworzyć list w udostępnionej książce adresowej."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Nie masz pozwolenia na dostęp do tego modułu lub tego systemu. Skontaktuj się ze swoim administratorem."; @@ -65,6 +66,12 @@ "delegate is a participant" = "Delegat jest już uczestnikiem."; "delegate is a group" = "Wskazany adres jest grupą. Możesz oddelegować tylko pojedynczną osobę."; +/* common buttons */ +"OK" = "OK"; +"Cancel" = "Cancel"; +"Yes" = "Yes"; +"No" = "No"; + /* alarms */ "Reminder:" = "Przypomnienie:"; "Start:" = "Początek:"; diff --git a/UI/Common/Russian.lproj/Localizable.strings b/UI/Common/Russian.lproj/Localizable.strings index d08f0ed77..f1278b323 100644 --- a/UI/Common/Russian.lproj/Localizable.strings +++ b/UI/Common/Russian.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Вам не предоставлено право доступа к этому модулю/системе. Пожалуйста, свяжитесь с администратором."; diff --git a/UI/Common/Spanish.lproj/Localizable.strings b/UI/Common/Spanish.lproj/Localizable.strings index a7167769c..3332a0e4e 100644 --- a/UI/Common/Spanish.lproj/Localizable.strings +++ b/UI/Common/Spanish.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "You are not allowed to access this module or this system. Please contact your system administrator."; diff --git a/UI/Common/Swedish.lproj/Localizable.strings b/UI/Common/Swedish.lproj/Localizable.strings index d1886330a..4b9eb29d5 100644 --- a/UI/Common/Swedish.lproj/Localizable.strings +++ b/UI/Common/Swedish.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "En mapp med det namnet finns redan."; "You cannot create a list in a shared address book." = "Du kan inte skapa en lista i en delad adressbok."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Du har inte åtkomsträttighet till modulen eller systemet. Kontakta din systemadministratör."; diff --git a/UI/Common/Welsh.lproj/Localizable.strings b/UI/Common/Welsh.lproj/Localizable.strings index bf0c34925..1dc5cdc63 100644 --- a/UI/Common/Welsh.lproj/Localizable.strings +++ b/UI/Common/Welsh.lproj/Localizable.strings @@ -54,6 +54,7 @@ "A folder by that name already exists." = "A folder by that name already exists."; "You cannot create a list in a shared address book." = "You cannot create a list in a shared address book."; +"Warning" = "Warning"; "You are not allowed to access this module or this system. Please contact your system administrator." = "Nid oes gennych caniatad mynediad i'r modiwl hwn na'r system hwn. Cysylltwch a'r Gweinyddwr Systemau os gwelwch yn dda."; diff --git a/UI/Contacts/Polish.lproj/Localizable.strings b/UI/Contacts/Polish.lproj/Localizable.strings index 4e322adb5..6ef1a6fdb 100644 --- a/UI/Contacts/Polish.lproj/Localizable.strings +++ b/UI/Contacts/Polish.lproj/Localizable.strings @@ -38,7 +38,7 @@ "invalidemailwarn" = "Podany adres e-mail jest nieprawidłowy"; "invaliddatewarn" = "Podana data jest nieprawidłowa."; "new" = "nowy"; -"Preferred Phone" = "Preferowany telefon"; +"Preferred Phone" = "Preferowany telefon"; "Move To" = "Przenieś do"; "Copy To" = "Kopiuj do"; diff --git a/UI/MailPartViewers/Polish.lproj/Localizable.strings b/UI/MailPartViewers/Polish.lproj/Localizable.strings index 93ee633ce..6444332ee 100644 --- a/UI/MailPartViewers/Polish.lproj/Localizable.strings +++ b/UI/MailPartViewers/Polish.lproj/Localizable.strings @@ -26,6 +26,7 @@ Tentative = "Niepewny"; "Delegate ..." = "Przekaż ..."; "Delegated to" = "Przekazane do"; "Update status in calendar" = "Zaktualizuj status w kalendarzu"; +"delegated from" = "delegated from"; reply_info_no_attendee = "Otrzymałeś odpowiedź do wydarzenia ale nadawca nie jest uczestnikiem."; reply_info = "To jest odpowiedź do utworzonego przez ciebie wydarzenia"; diff --git a/UI/MailPartViewers/Ukrainian.lproj/Localizable.strings b/UI/MailPartViewers/Ukrainian.lproj/Localizable.strings index 5f8f08be3..8ac165cc5 100644 --- a/UI/MailPartViewers/Ukrainian.lproj/Localizable.strings +++ b/UI/MailPartViewers/Ukrainian.lproj/Localizable.strings @@ -26,6 +26,7 @@ Tentative = "попередьно погодитись"; "Delegate ..." = "Делегуват ..."; "Delegated to" = "Делегувати"; "Update status in calendar" = "Поновити статус в календарі"; +"delegated from" = "delegated from"; reply_info_no_attendee = "Ви отримали відповідь на запланований захід, але відправник повідомлення відсутній серед запрошених."; reply_info = "Це відповідь на Ваше запрошення взяти участь у заході."; diff --git a/UI/MailerUI/Ukrainian.lproj/Localizable.strings b/UI/MailerUI/Ukrainian.lproj/Localizable.strings index 96264d1ee..18795da8a 100644 --- a/UI/MailerUI/Ukrainian.lproj/Localizable.strings +++ b/UI/MailerUI/Ukrainian.lproj/Localizable.strings @@ -45,7 +45,7 @@ "Home" = "Початок"; "Calendar" = "Календар"; -"Addressbook" = "Адресна"; +"Addressbook" = "Адресна книга"; "Mail" = "Пошта"; "Right Administration" = "Права доступу"; @@ -136,6 +136,7 @@ "View" = "Перегляд"; "All" = "Всі"; "Unread" = "Непрочитані"; +"No message" = "No message"; "messages" = "повідомлення"; "first" = "перша"; diff --git a/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings b/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings index 5bd1d5c22..b97114405 100644 --- a/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/PreferencesUI/BrazilianPortuguese.lproj/Localizable.strings @@ -134,7 +134,7 @@ "Port:" = "Port:"; "User Name:" = "User Name:"; "Password:" = "Senha:"; - + "Full Name:" = "Full Name:"; "Email:" = "Email:"; "Signature:" = "Assinatura:"; diff --git a/UI/PreferencesUI/Italian.lproj/Localizable.strings b/UI/PreferencesUI/Italian.lproj/Localizable.strings index 0795ca1af..49ec77700 100644 --- a/UI/PreferencesUI/Italian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Italian.lproj/Localizable.strings @@ -134,7 +134,7 @@ "Port:" = "Port:"; "User Name:" = "User Name:"; "Password:" = "Password:"; - + "Full Name:" = "Full Name:"; "Email:" = "Email:"; "Signature:" = "Firma:"; diff --git a/UI/PreferencesUI/Swedish.lproj/Localizable.strings b/UI/PreferencesUI/Swedish.lproj/Localizable.strings index 48d43dd7b..d1b38762c 100644 --- a/UI/PreferencesUI/Swedish.lproj/Localizable.strings +++ b/UI/PreferencesUI/Swedish.lproj/Localizable.strings @@ -134,7 +134,7 @@ "Port:" = "Port:"; "User Name:" = "User Name:"; "Password:" = "Lösenord:"; - + "Full Name:" = "Full Name:"; "Email:" = "Email:"; "Signature:" = "Signatur:"; diff --git a/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings b/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings index 84f205f2d..f6805fe9f 100644 --- a/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings +++ b/UI/PreferencesUI/Ukrainian.lproj/Localizable.strings @@ -16,7 +16,6 @@ "Add" = "Додати"; "Delete" = "Вилучити"; - /* vacation (auto-reply) */ "Enable vacation auto reply" = "Увімкнути повідомлення про мою відсутність"; "Auto reply message :" = "Текст автоматичної відповіді:"; @@ -34,7 +33,6 @@ "Please specify an address to which you want to forward your messages." = "Будь ласка, зазначте адресу, на яку потрібно перенаправляти повідомлення, адресовані Вам."; - /* d & t */ "Current Time Zone :" = "Поточна часова зона :"; "Short Date Format :" = "Стислий формат дати :"; @@ -136,7 +134,7 @@ "Port:" = "Port:"; "User Name:" = "User Name:"; "Password:" = "Пароль:"; - + "Full Name:" = "Full Name:"; "Email:" = "Email:"; "Signature:" = "Підпис:"; @@ -145,7 +143,6 @@ "Signature" = "Підпис"; "Please enter your signature below:" = "Please enter your signature below:"; - /* Additional Parameters */ "Additional Parameters" = "Додаткові параметри"; @@ -163,7 +160,7 @@ "Contacts" = "Адресна книга"; "Mail" = "Електронна пошта"; "Last" = "Останнє"; -"Default module :" = "Модуль за замовчанням :"; +"Default module :" = "Модуль за замовчанням :"; "Language :" = "Language :"; "choose" = "Choose ..."; diff --git a/UI/PreferencesUI/Welsh.lproj/Localizable.strings b/UI/PreferencesUI/Welsh.lproj/Localizable.strings index 799ea5a0f..98d413567 100644 --- a/UI/PreferencesUI/Welsh.lproj/Localizable.strings +++ b/UI/PreferencesUI/Welsh.lproj/Localizable.strings @@ -81,7 +81,8 @@ "Day start time must be prior to day end time." = "Day start time must be prior to day end time."; "First week of year :" = "Wythnos cyntaf y flwyddyn :"; "Enable reminders for Calendar items" = "Galluogu atgoffa ar gyfer eitemau calendr"; -"Play a sound when a reminder comes due" = "Chwarae swn pan fydd amser atgoffa"; +"Play a sound when a reminder comes due" += "Chwarae swn pan fydd amser atgoffa"; "Default reminder :" = "Atgoffa gwreiddiol :"; "firstWeekOfYear_January1" = "Dechrau ar Ionawr 1"; @@ -133,7 +134,7 @@ "Port:" = "Port:"; "User Name:" = "User Name:"; "Password:" = "Cyfrinair:"; - + "Full Name:" = "Full Name:"; "Email:" = "Email:"; "Signature:" = "Llofnod:"; diff --git a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings index ffd5c47eb..0f99efbcb 100644 --- a/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings +++ b/UI/Scheduler/BrazilianPortuguese.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Tarefa Confidencial)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Czech.lproj/Localizable.strings b/UI/Scheduler/Czech.lproj/Localizable.strings index f1d3aa459..6513c9920 100644 --- a/UI/Scheduler/Czech.lproj/Localizable.strings +++ b/UI/Scheduler/Czech.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Důvěrný úkol)"; "tagHasChanged" = "Změníte-li štítek svého kalendáře, budete muset znovu načíst data do svého mobilního zařízení.\nPokračovat?"; "tagWasAdded" = "Chcete-li synchronizovat tento kalendář, budete muset znovu načíst data do svého mobilního zařízení.\nPokračovat?"; "tagWasRemoved" = "Odstraníte-li u tohoto kalendáře synchronizaci, budete muset znovu načíst data do svého mobilního zařízení.\nPokračovat?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Otevřít úkol..."; "Mark Completed" = "Označit jako dokončené"; diff --git a/UI/Scheduler/Dutch.lproj/Localizable.strings b/UI/Scheduler/Dutch.lproj/Localizable.strings index 90bc1772f..8d7198602 100644 --- a/UI/Scheduler/Dutch.lproj/Localizable.strings +++ b/UI/Scheduler/Dutch.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Vertrouwelijke taak)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Taak openen..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index bc9d3c875..972ab91f2 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -555,6 +555,8 @@ vtodo_class2 = "(Confidential task)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index a8fc90d22..948ddac12 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -555,6 +555,8 @@ vtodo_class2 = "(Tâche confidentielle)"; "tagHasChanged" = "En changeant le label de ce calendrier, vous devrez recharger les données sur votre appareil mobile.\nVoulez-vous continuer?"; "tagWasAdded" = "Afin de synchroniser ce calendrier, vous devrez recharger les données sur votre appareil mobile.\nVoulez-vous continuer?"; "tagWasRemoved" = "Afin de ne plus synchroniser ce calendrier, vous devrez recharger les données sur votre appareil mobile.\nVoulez-vous continuer?"; +"DestinationCalendarError" = "Le calendrier de destination est le même que celui de l'événement. Choisissez un calendrier de destination différent."; +"EventCopyError" = "La copie a échouée. Choisissez un calendrier de destination différent."; "Open Task..." = "Ouvrir la tâche..."; "Mark Completed" = "Marquer comme accomplie"; diff --git a/UI/Scheduler/German.lproj/Localizable.strings b/UI/Scheduler/German.lproj/Localizable.strings index ee3d78d95..6465c282f 100644 --- a/UI/Scheduler/German.lproj/Localizable.strings +++ b/UI/Scheduler/German.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Vertrauliche Aufgabe)"; "tagHasChanged" = "Wenn der Tag des Kalenders geändert wird, müssen die Daten erneut auf das Mobilgerät geladen werden.\nFortfahren?"; "tagWasAdded" = "Wenn der Kalender synchronisiert werden soll, müssen die Daten erneut auf das Mobilgerät geladen werden.\nFortfahren?"; "tagWasRemoved" = "Wenn der Kalender aus der Synchronisation entfernt wird, müssen die Daten erneut auf das Mobilgerät geladen werden.\nFortfahren?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Aufgabe öffnen..."; "Mark Completed" = "Als abgeschlossen markieren"; diff --git a/UI/Scheduler/Hungarian.lproj/Localizable.strings b/UI/Scheduler/Hungarian.lproj/Localizable.strings index 081fb6318..2028f1137 100644 --- a/UI/Scheduler/Hungarian.lproj/Localizable.strings +++ b/UI/Scheduler/Hungarian.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Bizalmas feladat)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Italian.lproj/Localizable.strings b/UI/Scheduler/Italian.lproj/Localizable.strings index e7aac9d88..e68467cf2 100644 --- a/UI/Scheduler/Italian.lproj/Localizable.strings +++ b/UI/Scheduler/Italian.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Attività confidenziale)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Polish.lproj/Localizable.strings b/UI/Scheduler/Polish.lproj/Localizable.strings index 8cd95872c..bf35c1135 100644 --- a/UI/Scheduler/Polish.lproj/Localizable.strings +++ b/UI/Scheduler/Polish.lproj/Localizable.strings @@ -555,6 +555,8 @@ vtodo_class2 = "(Zadanie poufne)"; "tagHasChanged" = "Jeśli zmienisz znacznik kalendarza, musisz przeładować dane na swoim urządzeniu mobilnym.\nKontynuować?"; "tagWasAdded" = "Jeśli chesz synchronizować ten kalendarz, będziesz musiał(a) przeładować dane na swoim urządzeniu mobilnym.\nKontynuować?"; "tagWasRemoved" = "Jeśli usuniesz ten kalendarz z synchronizacji, będziesz musiał(a) przeładować dane na swoim urządzeniu mobilnym.\nKontynuować?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Otwórz zadanie..."; "Mark Completed" = "Oznacz jako ukończone"; diff --git a/UI/Scheduler/Russian.lproj/Localizable.strings b/UI/Scheduler/Russian.lproj/Localizable.strings index c90ce6e6a..5ca65d43f 100644 --- a/UI/Scheduler/Russian.lproj/Localizable.strings +++ b/UI/Scheduler/Russian.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Confidential task)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Spanish.lproj/Localizable.strings b/UI/Scheduler/Spanish.lproj/Localizable.strings index b5d094681..b8d665114 100644 --- a/UI/Scheduler/Spanish.lproj/Localizable.strings +++ b/UI/Scheduler/Spanish.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Tarea confidencial)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to reload the data on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/Swedish.lproj/Localizable.strings b/UI/Scheduler/Swedish.lproj/Localizable.strings index 67f218f30..55ec1e591 100644 --- a/UI/Scheduler/Swedish.lproj/Localizable.strings +++ b/UI/Scheduler/Swedish.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Konfidentiell uppgift)"; "tagHasChanged" = "Om du ändrar etiketten på din kalender, behöver du ladda om datat i din mobiltelefon.\nFortsätta?"; "tagWasAdded" = "Om du vill synkronisera kalendern, behöver du ladda om datat i din mobiltelefon.\nFortsätta?"; "tagWasRemoved" = "Om du tar bort kalendern från synkronisering, behöver du ladda om datat i din mobiltelefon.\nFortsätta?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Öppna uppgift..."; "Mark Completed" = "Märk utförd"; diff --git a/UI/Scheduler/UIxAppointmentActions.m b/UI/Scheduler/UIxAppointmentActions.m index 2513d5942..0a5604244 100644 --- a/UI/Scheduler/UIxAppointmentActions.m +++ b/UI/Scheduler/UIxAppointmentActions.m @@ -24,9 +24,12 @@ #import #import +#import +#import #import #import +#import #import #import @@ -108,4 +111,83 @@ return response; } +- (WOResponse *) copyAction +{ + NSString *destination; + NSArray *events; + iCalCalendar *calendar; + iCalRepeatableEntityObject *masterOccurence; + SOGoAppointmentObject *thisEvent; + SOGoAppointmentFolder *sourceCalendar, *destinationCalendar; + SoSecurityManager *sm; + WOResponse *response; + WORequest *rq; + + rq = [context request]; + + destination = [rq formValueForKey: @"destination"]; + if (![destination length]) + destination = @"personal"; + + thisEvent = [self clientObject]; + sourceCalendar = [thisEvent container]; + destinationCalendar = [[sourceCalendar container] lookupName: destination + inContext: context + acquire: NO]; + if (destinationCalendar) + { + // Verify access rights to destination calendar + sm = [SoSecurityManager sharedSecurityManager]; + if ([sm validatePermission: SoPerm_AddDocumentsImagesAndFiles + onObject: destinationCalendar + inContext: context]) + { + response = [NSException exceptionWithHTTPStatus: 403 + reason: @"Can't add event to destination calendar."]; + } + // Verify that the destination calendar is not the source calendar + else if ([[destinationCalendar nameInContainer] isEqualToString: [sourceCalendar nameInContainer]]) + { + response = [NSException exceptionWithHTTPStatus: 400 + reason: @"Destination calendar is the source calendar."]; + } + else + { + // Remove attendees, recurrence exceptions and single occurences from the event + calendar = [thisEvent calendar: NO secure: NO]; + events = [calendar events]; + masterOccurence = [events objectAtIndex: 0]; + + if ([masterOccurence hasAlarms]) + [masterOccurence removeAllAlarms]; + if ([masterOccurence hasRecurrenceRules]) + { + [masterOccurence removeAllExceptionRules]; + [masterOccurence removeAllExceptionDates]; + } + if ([[masterOccurence attendees] count] > 0) + { + [masterOccurence setOrganizer: nil]; + [masterOccurence removeAllAttendees]; + } + [calendar setUniqueChild: masterOccurence]; + + // Perform the copy + if ([thisEvent copyComponent: calendar + toFolder: (SOGoGCSFolder *) destinationCalendar]) + response = [NSException exceptionWithHTTPStatus: 500 + reason: @"Can't copy event to destination calendar."]; + else + response = [self responseWith204]; + } + } + else + { + response = [NSException exceptionWithHTTPStatus: 404 + reason: @"Can't find destination calendar."]; + } + + return response; +} + @end diff --git a/UI/Scheduler/Ukrainian.lproj/Localizable.strings b/UI/Scheduler/Ukrainian.lproj/Localizable.strings index 48a062718..766c43f16 100644 --- a/UI/Scheduler/Ukrainian.lproj/Localizable.strings +++ b/UI/Scheduler/Ukrainian.lproj/Localizable.strings @@ -540,6 +540,8 @@ vtodo_class2 = "(Конфіденційне завдання)"; "tagHasChanged" = "Якщо Ви змінете теґ вашого календаря, то потрібно буде перевантажити дані на Вашому мобільному пристрої.\nПродовжити?"; "tagWasAdded" = "Якшр Ви хочете синхронізувати цей календар, то потрібно буде перевантажити дані на Вашому мобільному пристрої.\nПродовжити?"; "tagWasRemoved" = "Якщо Ви скасуєте режим синхронізації для цього календаря, то потрібно буде перевантажити дані на Вашому мобільному пристрої.\nПродовжити?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Відкрити завдання..."; "Mark Completed" = "Позначити виконаним"; diff --git a/UI/Scheduler/Welsh.lproj/Localizable.strings b/UI/Scheduler/Welsh.lproj/Localizable.strings index e23659d58..c6bccec11 100644 --- a/UI/Scheduler/Welsh.lproj/Localizable.strings +++ b/UI/Scheduler/Welsh.lproj/Localizable.strings @@ -554,6 +554,8 @@ vtodo_class2 = "(Tasg gyhoeddus)"; "tagHasChanged" = "If you change your calendar's tag, you'll need to perform a slow sync on your mobile device.\nContinue?"; "tagWasAdded" = "If you want to synchronize this calendar, you'll need to reload the data on your mobile device.\nContinue?"; "tagWasRemoved" = "If you remove this calendar from synchronization, you'll need to perform a slow sync on your mobile device.\nContinue?"; +"DestinationCalendarError" = "The source and destination calendars are the same. Please try to copy to a different calendar."; +"EventCopyError" = "The copy failed. Please try to copy to a difference calendar."; "Open Task..." = "Open Task..."; "Mark Completed" = "Mark Completed"; diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index d4de77fad..266f4cd4f 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -220,6 +220,11 @@ pageName = "UIxAppointmentEditor"; actionName = "save"; }; + copy = { + protectedBy = "ViewAllComponent"; + actionClass = "UIxAppointmentActions"; + actionName = "copy"; + }; accept = { protectedBy = "RespondToComponent"; pageName = "UIxAppointmentEditor"; diff --git a/UI/Templates/SchedulerUI/UIxCalDayView.wox b/UI/Templates/SchedulerUI/UIxCalDayView.wox index 42c405dbd..34f291a9d 100644 --- a/UI/Templates/SchedulerUI/UIxCalDayView.wox +++ b/UI/Templates/SchedulerUI/UIxCalDayView.wox @@ -16,6 +16,7 @@
  • +
  • +
  • +
  • 0) { - for (var i = 0; i < rows.length; i++) { - var uid = rows[i].readAttribute("id").substr(4); - var path = Mailer.currentMailbox + "/" + uid; + if (rowIds.length > 0) { + for (var i = 0; i < rowIds.length; i++) { + if (unseenCount < 1) { + var rows = messageList.select('#' + rowIds[i]); + if (rows.length > 0) { + var row = rows.first(); + row.hide(); + if (row.hasClassName("mailer_unreadmail")) + unseenCount--; + } + else { + unseenCount = 1; + } + } deleteMessageRequestCount++; - rows[i].hide(); + var uid = rowIds[i].substr(4); // drop "row_" + var path = Mailer.currentMailbox + "/" + uid; uids.push(uid); paths.push(path); - if (rows[i].hasClassName("mailer_unreadmail")) - unseenCount--; } messageList.deselectAll(); - updateMessageListCounter(0 - rows.length, true); + updateMessageListCounter(0 - rowIds.length, true); if (unseenCount < 0) { var node = mailboxTree.getMailboxNode(Mailer.currentMailbox); if (node) { @@ -389,7 +403,7 @@ function deleteSelectedMessages(sender) { } var url = ApplicationBaseURL + encodeURI(Mailer.currentMailbox) + "/batchDelete"; var parameters = "uid=" + uids.join(","); - var data = { "id": uids, "mailbox": Mailer.currentMailbox, "path": paths }; + var data = { "id": uids, "mailbox": Mailer.currentMailbox, "path": paths, "refreshUnseenCount": (unseenCount > 0) }; triggerAjaxRequest(url, deleteSelectedMessagesCallback, data, parameters, { "Content-type": "application/x-www-form-urlencoded" }); } @@ -436,6 +450,8 @@ function deleteSelectedMessagesCallback(http) { } } } + if (data["refreshUnseenCount"]) + getUnseenCountForFolder(data["mailbox"]); } else { log ("deleteSelectedMessagesCallback: problem during ajax request " + http.status); @@ -470,11 +486,11 @@ function deleteMessageWithDelay(url, id, mailbox, messageId) { function onPrintCurrentMessage(event) { var messageList = $("messageListBody").down("TBODY"); - var rowIds = messageList.getSelectedNodes(); - if (rowIds.length == 0) { + var rows = messageList.getSelectedNodes(); + if (rows.length == 0) { showAlertDialog(_("Please select a message to print.")); } - else if (rowIds.length > 1) { + else if (rows.length > 1) { showAlertDialog(_("Please select only one message to print.")); } else @@ -531,7 +547,7 @@ function toggleAddressColumn(search, replace) { function onMailboxMenuMove(event) { var targetMailbox; var messageList = $("messageListBody").down("TBODY"); - var rows = messageList.getSelectedNodes(); + var rowIds = messageList.getSelectedNodesId(); var uids = new Array(); // message IDs var paths = new Array(); // row IDs @@ -543,10 +559,12 @@ function onMailboxMenuMove(event) { else // from DnD targetMailbox = this.readAttribute("dataname"); - for (var i = 0; i < rows.length; i++) { - var uid = rows[i].readAttribute("id").substr(4); + for (var i = 0; i < rowIds.length; i++) { + var uid = rowIds[i].substr(4); var path = Mailer.currentMailbox + "/" + uid; - rows[i].hide(); + var rows = messageList.select('#' + rowIds[i]); + if (rows.length > 0) + rows.first().hide(); uids.push(uid); paths.push(path); // Remove references to closed popups @@ -576,7 +594,7 @@ function onMailboxMenuMove(event) { function onMailboxMenuCopy(event) { var messageList = $("messageListBody").down("TBODY"); - var rows = messageList.getSelectedNodes(); + var rowIds = messageList.getSelectedNodesId(); var uids = new Array(); // message IDs var paths = new Array(); // row IDs @@ -585,8 +603,8 @@ function onMailboxMenuCopy(event) { targetMailbox = this.mailbox.fullName(); else // from DnD targetMailbox = this.readAttribute("dataname"); - for (var i = 0; i < rows.length; i++) { - var uid = rows[i].readAttribute("id").substr(4); + for (var i = 0; i < rowIds.length; i++) { + var uid = rowIds[i].substr(4); var path = Mailer.currentMailbox + "/" + uid; uids.push(uid); paths.push(path); @@ -2099,9 +2117,7 @@ function onMenuEmptyTrash(event) { triggerAjaxRequest(urlstr, folderOperationCallback, errorLabel); if (folderID == Mailer.currentMailbox) { - var div = $('messageContent'); - for (var i = div.childNodes.length - 1; i > -1; i--) - div.removeChild(div.childNodes[i]); + $('messageContent').update(); } var msgID = Mailer.currentMessages[folderID]; if (msgID) @@ -2230,6 +2246,7 @@ function folderRefreshCallback(http) { var oldMailbox = http.callbackData.mailbox; if (http.callbackData.refresh && oldMailbox == Mailer.currentMailbox) { + getUnseenCountForFolder(oldMailbox); if (http.callbackData.id) { var s = http.callbackData.id + ""; var uids = s.split(","); @@ -2248,7 +2265,8 @@ function folderRefreshCallback(http) { var uids = s.split(","); for (var i = 0; i < uids.length; i++) { var row = $("row_" + uids[i]); - row.show(); + if (row) + row.show(); } } showAlertDialog(_("Operation failed")); @@ -2622,7 +2640,7 @@ function startDragging (itm, e) { var handle = $("dragDropVisual"); var count = $("messageListBody").getSelectedRowsId().length; - handle.update (count); + handle.update(count); if (Mailer.currentMailbox) { var parts = Mailer.currentMailbox.split("/"); handle.addClassName("account" + parts[1]); diff --git a/UI/WebServerResources/SOGoDataTable.js b/UI/WebServerResources/SOGoDataTable.js index 4891788ac..4be4908ff 100644 --- a/UI/WebServerResources/SOGoDataTable.js +++ b/UI/WebServerResources/SOGoDataTable.js @@ -33,9 +33,51 @@ var SOGoDataTableInterface = { this.body = this.down("tbody"); this.rowModel = this.body.down("tr"); + /** + * Overrided methods from HTMLElement.js + * Handle selection based on rows ID. + */ + this.body.selectRange = function(startIndex, endIndex) { + var element = $(this); + var s; + var e; + var rows; + var div = this.up('div'); + var uid = lastClickedRowId.substr(4); + + startIndex = div.dataSource.indexOf(uid); + uid = div.down('tr', endIndex).id.substr(4); + endIndex = div.dataSource.indexOf(uid); + + if (startIndex > endIndex) { + s = endIndex; + e = startIndex; + } + else { + s = startIndex; + e = endIndex; + } + + while (s <= e) { + uid = "row_" + div.dataSource.uids[s]; + if (this.selectedIds.indexOf(uid) < 0) + this.selectedIds.push(uid); + s++; + } + this.refreshSelectionByIds(); + }; + + this.body.selectAll = function() { + var div = this.up('div'); + this.selectedIds = new Array(); + for (var i = 0; i < div.dataSource.uids.length; i++) + this.selectedIds.push("row_" + div.dataSource.uids[i]); + this.refreshSelectionByIds(); + }, + // Since we use the fixed table layout, the first row must have the // proper CSS classes that will define the columns width. - this.rowTop = new Element('tr', {'id': 'rowTop'});//.update(new Element('td')); + this.rowTop = new Element('tr', {'id': 'rowTop'}); this.body.insertBefore(this.rowTop, this.rowModel); // IE requires the element to be inside the DOM before appending new children var cells = this.rowModel.select('TD'); for (var i = 0; i < cells.length; i++) { @@ -60,6 +102,7 @@ var SOGoDataTableInterface = { setSource: function(ds) { this.dataSource = ds; + this.currentRenderID = ""; this._emptyTable(); this.scrollTop = 0; }, @@ -135,7 +178,6 @@ var SOGoDataTableInterface = { var scroll; scroll = this.scrollTop; - lastClickedRow = -1; // defined in generic.js h = start * this.rowHeight; if (Prototype.Browser.IE) diff --git a/UI/WebServerResources/SchedulerUI.js b/UI/WebServerResources/SchedulerUI.js index 4f4d20898..fd166d04e 100644 --- a/UI/WebServerResources/SchedulerUI.js +++ b/UI/WebServerResources/SchedulerUI.js @@ -3,7 +3,7 @@ var listFilter = 'view_today'; var listOfSelection = null; -var selectedCalendarCell; +var selectedCalendarCell = null; var showCompletedTasks; @@ -32,6 +32,9 @@ var calendarHeaderAdjusted = false; var categoriesStyles = new Hash(); var categoriesStyleSheet = null; +var clipboard = null; +var eventsToCopy = []; + function newEvent(type, day, hour, duration) { var folder = getSelectedFolder(); var folderID = folder.readAttribute("id"); @@ -129,7 +132,7 @@ function getSelectedFolder() { if (nodes.length > 0) folder = nodes[0]; else - folder = list.down("li"); + folder = list.down("li"); // personal calendar return folder; } @@ -198,10 +201,6 @@ function deleteEvent() { var nodes = listOfSelection.getSelectedRows(); if (nodes.length > 0) { var label = ""; - if (!nodes[0].erasable && !IsSuperUser) { - showAlertDialog(_("You don't have the required privileges to perform the operation.")); - return false; - } if (listOfSelection == $("tasksList")) label = _("taskDeleteConfirmation"); else @@ -209,17 +208,18 @@ function deleteEvent() { if (nodes.length == 1 && nodes[0].recurrenceTime) { - _editRecurrenceDialog(nodes[0], "confirmDeletion"); + if (nodes[0].erasable) + _editRecurrenceDialog(nodes[0], "confirmDeletion"); + else + showAlertDialog(_("You don't have the required privileges to perform the operation.")); } else { - if (confirm(label)) { - if (document.deleteEventAjaxRequest) { - document.deleteEventAjaxRequest.aborted = true; - document.deleteEventAjaxRequest.abort(); - } - var sortedNodes = []; - var calendars = []; - for (var i = 0; i < nodes.length; i++) { + var canDelete; + var sortedNodes = []; + var calendars = []; + for (var i = 0; i < nodes.length; i++) { + canDelete = nodes[i].erasable || IsSuperUser; + if (canDelete) { var calendar = nodes[i].calendar; if (!sortedNodes[calendar]) { sortedNodes[calendar] = []; @@ -227,32 +227,34 @@ function deleteEvent() { } sortedNodes[calendar].push(nodes[i].cname); } - for (var i = 0; i < calendars.length; i++) { - calendarsOfEventsToDelete.push(calendars[i]); - eventsToDelete.push(sortedNodes[calendars[i]]); - } - _batchDeleteEvents(); } + for (i = 0; i < calendars.length; i++) { + calendarsOfEventsToDelete.push(calendars[i]); + eventsToDelete.push(sortedNodes[calendars[i]]); + } + if (i > 0) + showConfirmDialog(_("Warning"), label, deleteEventFromListConfirm); + else + showAlertDialog(_("You don't have the required privileges to perform the operation.")); } - } else { - showAlertDialog(_("Please select an event or a task.")); } + else + showAlertDialog(_("Please select an event or a task.")); } else if (selectedCalendarCell) { if (selectedCalendarCell.length == 1 && selectedCalendarCell[0].recurrenceTime) { - _editRecurrenceDialog(selectedCalendarCell[0], "confirmDeletion"); + if (selectedCalendarCell[0].erasable) + _editRecurrenceDialog(selectedCalendarCell[0], "confirmDeletion"); + else + showAlertDialog(_("You don't have the required privileges to perform the operation.")); } - else if (confirm(_("eventDeleteConfirmation"))) { - if (document.deleteEventAjaxRequest) { - document.deleteEventAjaxRequest.aborted = true; - document.deleteEventAjaxRequest.abort(); - } - var canDelete = true; + else { + var canDelete; var sortedNodes = []; var calendars = []; for (var i = 0; i < selectedCalendarCell.length; i++) { - canDelete = canDelete && (selectedCalendarCell[i].erasable || IsSuperUser); + canDelete = selectedCalendarCell[i].erasable || IsSuperUser; if (canDelete) { var calendar = selectedCalendarCell[i].calendar; if (!sortedNodes[calendar]) { @@ -263,11 +265,14 @@ function deleteEvent() { } } - for (var i = 0; i < calendars.length; i++) { + for (i = 0; i < calendars.length; i++) { calendarsOfEventsToDelete.push(calendars[i]); eventsToDelete.push(sortedNodes[calendars[i]]); } - _batchDeleteEvents(); + if (i > 0) + showConfirmDialog(_("Warning"), _("eventDeleteConfirmation"), deleteEventFromViewConfirm); + else + showAlertDialog(_("You don't have the required privileges to perform the operation.")); } } else @@ -276,6 +281,84 @@ function deleteEvent() { return false; } +function deleteEventFromListConfirm() { + var nodes = listOfSelection.getSelectedRows(); + if (document.deleteEventAjaxRequest) { + document.deleteEventAjaxRequest.aborted = true; + document.deleteEventAjaxRequest.abort(); + } + _batchDeleteEvents(); + disposeDialog(); +} + +function deleteEventFromViewConfirm() { + if (document.deleteEventAjaxRequest) { + document.deleteEventAjaxRequest.aborted = true; + document.deleteEventAjaxRequest.abort(); + } + + selectedCalendarCell = null; + _batchDeleteEvents(); + disposeDialog(); +} + +function copyEventToClipboard() { + if (listOfSelection) { + clipboard = new Array(); + var nodes = listOfSelection.getSelectedRows(); + for (var i = 0; i < nodes.length; i++) + clipboard.push(nodes[i].calendar + "/" + nodes[i].cname); + } + else if (selectedCalendarCell) { + clipboard = new Array(); + for (var i = 0; i < selectedCalendarCell.length; i++) + clipboard.push(selectedCalendarCell[i].calendar + "/" + selectedCalendarCell[i].cname); + } + log ("clipboard : " + clipboard.join(", ")); +} + +function copyEventFromClipboard() { + if (clipboard && clipboard.length > 0) { + var folder = getSelectedFolder(); + var folderID = folder.readAttribute("id").substr(1); + eventsToCopy = []; + for (var i = 0; i < clipboard.length; i++) + eventsToCopy[i] = clipboard[i] + "/copy?destination=" + folderID; + copyEvents(); + } +} + +function copyEventToPersonalCalendar(event) { + var calendar = selectedCalendarCell[0].calendar; + var cname = selectedCalendarCell[0].cname; + eventsToCopy = [calendar + "/" + cname + "/copy"]; + copyEvents(); +} + +function copyEvents() { + var path = eventsToCopy.shift(); + var urlstr = ApplicationBaseURL + path; log (urlstr); + triggerAjaxRequest(urlstr, + copyEventCallback); +} + +function copyEventCallback(http) { + if (http.readyState == 4) { + if (isHttpStatus204(http.status)) { + if (eventsToCopy.length) + copyEvents(); + else + refreshEventsAndDisplay(); + } + else if (parseInt(http.status) == 403) + showAlertDialog(_("You don't have the required privileges to perform the operation.")); + else if (parseInt(http.status) == 400) + showAlertDialog(_("DestinationCalendarError")); + else + showAlertDialog(_("EventCopyError")); + } +} + function modifyEvent(sender, modification, parameters) { var currentLocation = '' + window.location; var arr = currentLocation.split("/"); @@ -1313,6 +1396,7 @@ function newBaseEventDIV(eventRep, event, eventText) { eventCell.calendar = event[1]; if (eventRep.recurrenceTime) eventCell.recurrenceTime = eventRep.recurrenceTime; + //eventCell.owner = event[12]; eventCell.isException = event[16]; eventCell.editable = event[17]; eventCell.erasable = event[18]; @@ -1533,36 +1617,22 @@ function calendarDisplayCallback(http) { currentDay = http.callbackData["day"]; // Initialize contextual menu - var menu; - var observer; - if (currentView == 'dayview') { - menu = new Array(onMenuNewEventClick, + var menu = new Array(onMenuNewEventClick, onMenuNewTaskClick, "-", loadPreviousView, loadNextView, "-", - deleteEvent); + deleteEvent, + copyEventToPersonalCalendar); + var observer; + if (currentView == 'dayview') { observer = $("daysView"); } else if (currentView == 'weekview') { - menu = new Array(onMenuNewEventClick, - onMenuNewTaskClick, - "-", - loadPreviousView, - loadNextView, - "-", - deleteEvent); observer = $("daysView"); } else { - menu = new Array(onMenuNewEventClick, - onMenuNewTaskClick, - "-", - loadPreviousView, - loadNextView, - "-", - deleteEvent); observer = $("monthDaysView"); } @@ -1588,8 +1658,10 @@ function calendarDisplayCallback(http) { attachDragControllers(contentView); // Attach contextual menu - initMenu($("currentViewMenu"), menu); + var currentViewMenu = $("currentViewMenu"); + initMenu(currentViewMenu, menu); observer.observe("contextmenu", onMenuCurrentView); + currentViewMenu.prepareVisibility = onMenuCurrentViewPrepareVisibility; restoreSelectedDay(); @@ -1654,12 +1726,20 @@ function onEventsSelectionChange() { this.removeClassName("_unfocused"); $("tasksList").addClassName("_unfocused"); + deselectAll(true); var rows = $(this.tBodies[0]).getSelectedNodes(); if (rows.length == 1) { var row = rows[0]; changeCalendarDisplay( { "day": row.day, "scrollEvent": row.getAttribute("id") } ); - changeDateSelectorDisplay(row.day); + changeDateSelectorDisplay(row.day, true); + } + else { + // Select visible events cells + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + selectCalendarEvent(row.calendar, row.cname, row.recurrenceTime); + } } } @@ -1862,8 +1942,9 @@ function selectCalendarEvent(calendar, cname, recurrenceTime) { if (selection) { for (var i = 0; i < selection.length; i++) selection[i].selectElement(); - if (selectedCalendarCell) + if (selectedCalendarCell) { selectedCalendarCell = selectedCalendarCell.concat(selection); + } else selectedCalendarCell = selection; } @@ -1894,8 +1975,18 @@ function onSelectAll(event) { return false; } +function deselectAll(cellsOnly) { + if (!cellsOnly && listOfSelection) { + listOfSelection.deselectAll(); + } + if (selectedCalendarCell) { + for (var i = 0; i < selectedCalendarCell.length; i++) + selectedCalendarCell[i].deselect(); + selectedCalendarCell = null; + } +} + function onCalendarSelectEvent(event) { - var list = $("eventsList").down("TBODY"); var alreadySelected = false; // Look for event in events list @@ -1927,13 +2018,11 @@ function onCalendarSelectEvent(event) { } else if (event.shiftKey == 0) { // Unselect entries in events list and calendar view - list.deselectAll(); - if (selectedCalendarCell) { - for (var i = 0; i < selectedCalendarCell.length; i++) - if (selectedCalendarCell[i] != this) - selectedCalendarCell[i].deselect(); - } - selectedCalendarCell = [this]; + listOfSelection = $("eventsList"); + deselectAll(); + this.selectElement(); + if (alreadySelected) + selectedCalendarCell = [this]; } if (!alreadySelected) { @@ -1951,10 +2040,21 @@ function onCalendarSelectEvent(event) { function onCalendarSelectDay(event) { var day = this.getAttribute("day"); setSelectedDayDate(day); - var needRefresh = (listFilter == 'view_selectedday' && day != currentDay); - changeDateSelectorDisplay(day); + var target = Event.findElement(event); + var div = target.up('div'); + if (div && !div.hasClassName('event') && !div.hasClassName('eventInside') && !div.hasClassName('text') && !div.hasClassName('gradient')) { + // Target is not an event -- unselect all events. + listOfSelection = $("eventsList"); + deselectAll(); + listOfSelection = null; + preventDefault(event); + return false; + } + + var needRefresh = (listFilter == 'view_selectedday' && day != currentDay); + if (listOfSelection) { listOfSelection.addClassName("_unfocused"); listOfSelection = null; @@ -2170,6 +2270,26 @@ function onCalendarsMenuPrepareVisibility() { return false; } +function onMenuCurrentViewPrepareVisibility() { + var options = $(this).down("ul"); + var deleteOption = options.down("li", 6); + var copyOption = options.down("li", 7); + if (!selectedCalendarCell) { + deleteOption.addClassName("disabled"); + copyOption.addClassName("disabled"); + } + else { + deleteOption.removeClassName("disabled"); + var calendarEntry = $("/" + selectedCalendarCell[0].calendar); + if (calendarEntry.getAttribute("owner") == UserLogin) + copyOption.addClassName("disabled"); + else + copyOption.removeClassName("disabled"); + } + + return true; +} + function getMenus() { var menus = {}; @@ -2245,10 +2365,11 @@ function onMenuSharing(event) { function onMenuCurrentView(event) { $("eventDialog").hide(); - var onClick = onCalendarSelectEvent.bind(this); - onClick(event); + if (this.hasClassName('event')) { + var onClick = onCalendarSelectEvent.bind(this); + onClick(event); + } popupMenu(event, 'currentViewMenu', this); - } function onMenuAllDayView(event) { @@ -2728,6 +2849,14 @@ function onDocumentKeydown(event) { onSelectAll(event); Event.stop(event); } + else if (((isMac() && event.metaKey == 1) || (!isMac() && event.ctrlKey == 1)) + && event.keyCode == 67) { // Ctrl-C + copyEventToClipboard(); + } + else if (((isMac() && event.metaKey == 1) || (!isMac() && event.ctrlKey == 1)) + && event.keyCode == 86) { // Ctrl-V + copyEventFromClipboard(); + } } } diff --git a/UI/WebServerResources/generic.js b/UI/WebServerResources/generic.js index 9ac02fd01..a128dcad6 100644 --- a/UI/WebServerResources/generic.js +++ b/UI/WebServerResources/generic.js @@ -33,6 +33,7 @@ var dialogActive = false; var dialogsStack = new Array(); var lastClickedRow = -1; +var lastClickedRowId = -1; // logArea = null; var allDocumentElements = null; @@ -552,10 +553,10 @@ function onRowClick(event) { return true; } - var initialSelection = $(node.parentNode).getSelectedNodes(); + var initialSelection = $(node.parentNode).getSelectedNodesId(); - if (initialSelection.length > 0 - && initialSelection.indexOf(node) >= 0 + if (initialSelection && initialSelection.length > 0 + && initialSelection.indexOf(node.id) >= 0 && !eventIsLeftClick(event)) // Ignore non primary-click (ie right-click) inside current selection return true; @@ -578,7 +579,7 @@ function onRowClick(event) { $(node.parentNode).deselectAll(); $(node).selectElement(); - if (initialSelection != $(node.parentNode).getSelectedNodes()) { + if (initialSelection != $(node.parentNode).getSelectedNodesId()) { // Selection has changed; fire mousedown event var parentNode = node.parentNode; if (parentNode.tagName == 'TBODY') @@ -586,8 +587,10 @@ function onRowClick(event) { parentNode.fire("mousedown"); } } - if (rowIndex != null) - lastClickedRow = rowIndex; + if (rowIndex != null) { + lastClickedRow = rowIndex; + lastClickedRowId = node.getAttribute("id"); + } return true; }