From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/calendar/.prettierrc | 7 + comm/calendar/base/calendar.js | 175 + comm/calendar/base/content/calendar-base-view.js | 647 ++ .../base/content/calendar-chrome-startup.js | 438 + comm/calendar/base/content/calendar-clipboard.js | 306 + .../base/content/calendar-command-controller.js | 869 ++ .../base/content/calendar-commands.inc.xhtml | 101 + .../calendar-context-menus-and-tooltips.inc.xhtml | 949 ++ comm/calendar/base/content/calendar-day-label.js | 128 + .../calendar/base/content/calendar-dnd-listener.js | 922 ++ .../base/content/calendar-editable-item.js | 464 + comm/calendar/base/content/calendar-extract.js | 266 + .../base/content/calendar-invitation-display.js | 169 + .../base/content/calendar-invitations-manager.js | 385 + comm/calendar/base/content/calendar-keys.inc.xhtml | 15 + comm/calendar/base/content/calendar-management.js | 721 ++ .../content/calendar-menu-events-tasks.inc.xhtml | 105 + comm/calendar/base/content/calendar-menus.js | 176 + comm/calendar/base/content/calendar-migration.js | 323 + comm/calendar/base/content/calendar-modes.js | 125 + comm/calendar/base/content/calendar-month-view.js | 1242 +++ .../base/content/calendar-multiday-view.js | 3512 +++++++ comm/calendar/base/content/calendar-print.js | 311 + .../base/content/calendar-status-bar.inc.xhtml | 75 + comm/calendar/base/content/calendar-statusbar.js | 110 + .../base/content/calendar-tab-panels.inc.xhtml | 661 ++ comm/calendar/base/content/calendar-tabs.js | 419 + .../base/content/calendar-task-tree-utils.js | 341 + .../base/content/calendar-task-tree-view.js | 495 + comm/calendar/base/content/calendar-task-tree.js | 685 ++ comm/calendar/base/content/calendar-task-view.js | 470 + .../base/content/calendar-today-pane.inc.xhtml | 179 + comm/calendar/base/content/calendar-ui-utils.js | 596 ++ comm/calendar/base/content/calendar-unifinder.js | 988 ++ .../base/content/calendar-view-menu.inc.xhtml | 195 + comm/calendar/base/content/calendar-views-utils.js | 617 ++ comm/calendar/base/content/calendar-views.js | 286 + .../base/content/dialogs/calendar-alarm-dialog.js | 484 + .../content/dialogs/calendar-alarm-dialog.xhtml | 53 + .../content/dialogs/calendar-conflicts-dialog.js | 43 + .../dialogs/calendar-conflicts-dialog.xhtml | 35 + .../base/content/dialogs/calendar-creation.js | 836 ++ .../base/content/dialogs/calendar-creation.xhtml | 259 + .../base/content/dialogs/calendar-dialog-utils.js | 662 ++ .../base/content/dialogs/calendar-error-prompt.js | 21 + .../content/dialogs/calendar-error-prompt.xhtml | 59 + .../dialogs/calendar-event-dialog-attendees.js | 1601 ++++ .../dialogs/calendar-event-dialog-attendees.xhtml | 227 + .../dialogs/calendar-event-dialog-recurrence.js | 1237 +++ .../dialogs/calendar-event-dialog-recurrence.xhtml | 1077 +++ .../dialogs/calendar-event-dialog-reminder.js | 508 + .../dialogs/calendar-event-dialog-reminder.xhtml | 148 + .../dialogs/calendar-event-dialog-timezone.js | 126 + .../dialogs/calendar-event-dialog-timezone.xhtml | 63 + .../content/dialogs/calendar-event-dialog.xhtml | 584 ++ .../content/dialogs/calendar-ics-file-dialog.js | 476 + .../content/dialogs/calendar-ics-file-dialog.xhtml | 137 + .../content/dialogs/calendar-identity-utils.js | 187 + .../content/dialogs/calendar-invitations-dialog.js | 310 + .../dialogs/calendar-invitations-dialog.xhtml | 53 + .../dialogs/calendar-itip-identity-dialog.js | 52 + .../dialogs/calendar-itip-identity-dialog.xhtml | 42 + .../content/dialogs/calendar-migration-dialog.js | 113 + .../dialogs/calendar-migration-dialog.xhtml | 49 + .../content/dialogs/calendar-occurrence-prompt.js | 62 + .../dialogs/calendar-occurrence-prompt.xhtml | 61 + .../content/dialogs/calendar-properties-dialog.js | 251 + .../dialogs/calendar-properties-dialog.xhtml | 257 + .../dialogs/calendar-providerUninstall-dialog.js | 58 + .../calendar-providerUninstall-dialog.xhtml | 45 + .../content/dialogs/calendar-summary-dialog.js | 381 + .../content/dialogs/calendar-summary-dialog.xhtml | 232 + .../dialogs/calendar-uri-redirect-dialog.js | 26 + .../dialogs/calendar-uri-redirect-dialog.xhtml | 46 + .../base/content/dialogs/chooseCalendarDialog.js | 89 + .../content/dialogs/chooseCalendarDialog.xhtml | 35 + .../calendar/base/content/dialogs/publishDialog.js | 68 + .../base/content/dialogs/publishDialog.xhtml | 51 + .../base/content/imip-bar-overlay.inc.xhtml | 296 + comm/calendar/base/content/imip-bar.js | 429 + comm/calendar/base/content/import-export.js | 330 + .../content/item-editing/calendar-item-editing.js | 849 ++ .../content/item-editing/calendar-item-iframe.js | 4302 +++++++++ .../item-editing/calendar-item-iframe.xhtml | 1225 +++ .../item-editing/calendar-item-panel.inc.xhtml | 130 + .../content/item-editing/calendar-item-panel.js | 1143 +++ .../item-editing/calendar-item-toolbar.inc.xhtml | 164 + .../content/item-editing/calendar-task-editing.js | 181 + .../base/content/preferences/alarms.inc.xhtml | 188 + comm/calendar/base/content/preferences/alarms.js | 165 + .../preferences/calendar-preferences.inc.xhtml | 55 + .../content/preferences/calendar-preferences.js | 28 + .../base/content/preferences/categories.inc.xhtml | 32 + .../base/content/preferences/categories.js | 297 + .../base/content/preferences/editCategory.js | 112 + .../base/content/preferences/editCategory.xhtml | 49 + .../base/content/preferences/general.inc.xhtml | 185 + comm/calendar/base/content/preferences/general.js | 148 + .../base/content/preferences/notifications.js | 24 + .../base/content/preferences/views.inc.xhtml | 311 + comm/calendar/base/content/preferences/views.js | 115 + comm/calendar/base/content/printing-template.html | 285 + comm/calendar/base/content/publish.js | 239 + comm/calendar/base/content/sound.wav | Bin 0 -> 66194 bytes comm/calendar/base/content/today-pane-agenda.js | 668 ++ comm/calendar/base/content/today-pane.js | 535 ++ .../base/content/widgets/calendar-alarm-widget.js | 402 + .../base/content/widgets/calendar-dnd-widgets.js | 192 + .../content/widgets/calendar-filter-tree-view.js | 371 + .../base/content/widgets/calendar-filter.js | 1365 +++ .../content/widgets/calendar-invitation-panel.js | 799 ++ .../widgets/calendar-invitation-panel.xhtml | 96 + .../base/content/widgets/calendar-item-summary.js | 761 ++ .../base/content/widgets/calendar-minidate.js | 83 + .../base/content/widgets/calendar-minidate.xhtml | 17 + .../base/content/widgets/calendar-minimonth.js | 1055 +++ .../base/content/widgets/calendar-modebox.js | 244 + .../widgets/calendar-notifications-setting.js | 259 + .../base/content/widgets/datetimepickers.js | 1529 +++ .../base/content/widgets/mouseoverPreviews.js | 439 + comm/calendar/base/jar.mn | 111 + comm/calendar/base/modules/Ical.jsm | 9707 ++++++++++++++++++++ .../base/modules/calCalendarDeactivator.jsm | 171 + comm/calendar/base/modules/calExtract.jsm | 1417 +++ comm/calendar/base/modules/calHashedArray.jsm | 258 + comm/calendar/base/modules/calRecurrenceUtils.jsm | 553 ++ comm/calendar/base/modules/calUtils.jsm | 578 ++ comm/calendar/base/modules/moz.build | 36 + comm/calendar/base/modules/utils/calACLUtils.jsm | 92 + comm/calendar/base/modules/utils/calAlarmUtils.jsm | 161 + comm/calendar/base/modules/utils/calAuthUtils.jsm | 564 ++ .../base/modules/utils/calCategoryUtils.jsm | 103 + comm/calendar/base/modules/utils/calDataUtils.jsm | 313 + .../base/modules/utils/calDateTimeFormatter.jsm | 620 ++ .../base/modules/utils/calDateTimeUtils.jsm | 430 + comm/calendar/base/modules/utils/calEmailUtils.jsm | 218 + .../base/modules/utils/calInvitationUtils.jsm | 875 ++ comm/calendar/base/modules/utils/calItemUtils.jsm | 675 ++ .../base/modules/utils/calIteratorUtils.jsm | 279 + comm/calendar/base/modules/utils/calItipUtils.jsm | 2181 +++++ comm/calendar/base/modules/utils/calL10NUtils.jsm | 162 + comm/calendar/base/modules/utils/calPrintUtils.jsm | 616 ++ .../modules/utils/calProviderDetectionUtils.jsm | 182 + .../base/modules/utils/calProviderUtils.jsm | 907 ++ .../base/modules/utils/calUnifinderUtils.jsm | 206 + comm/calendar/base/modules/utils/calViewUtils.jsm | 521 ++ .../calendar/base/modules/utils/calWindowUtils.jsm | 182 + comm/calendar/base/modules/utils/calXMLUtils.jsm | 188 + comm/calendar/base/moz.build | 45 + comm/calendar/base/public/calIAlarm.idl | 157 + comm/calendar/base/public/calIAlarmService.idl | 116 + comm/calendar/base/public/calIAttachment.idl | 57 + comm/calendar/base/public/calIAttendee.idl | 80 + comm/calendar/base/public/calICalendar.idl | 605 ++ .../base/public/calICalendarACLManager.idl | 86 + comm/calendar/base/public/calICalendarManager.idl | 139 + comm/calendar/base/public/calICalendarProvider.idl | 89 + comm/calendar/base/public/calICalendarView.idl | 216 + .../base/public/calICalendarViewController.idl | 75 + comm/calendar/base/public/calIChangeLog.idl | 151 + comm/calendar/base/public/calIDateTime.idl | 228 + comm/calendar/base/public/calIDeletedItems.idl | 26 + comm/calendar/base/public/calIDuration.idl | 104 + comm/calendar/base/public/calIErrors.idl | 117 + comm/calendar/base/public/calIEvent.idl | 42 + comm/calendar/base/public/calIFreeBusyProvider.idl | 109 + comm/calendar/base/public/calIICSService.idl | 242 + comm/calendar/base/public/calIIcsParser.idl | 81 + comm/calendar/base/public/calIIcsSerializer.idl | 73 + comm/calendar/base/public/calIImportExport.idl | 56 + comm/calendar/base/public/calIItemBase.idl | 372 + comm/calendar/base/public/calIItipItem.idl | 112 + comm/calendar/base/public/calIItipTransport.idl | 48 + comm/calendar/base/public/calIOperation.idl | 46 + comm/calendar/base/public/calIPeriod.idl | 58 + comm/calendar/base/public/calIRecurrenceDate.idl | 24 + comm/calendar/base/public/calIRecurrenceInfo.idl | 184 + comm/calendar/base/public/calIRecurrenceItem.idl | 60 + comm/calendar/base/public/calIRecurrenceRule.idl | 53 + comm/calendar/base/public/calIRelation.idl | 45 + .../calendar/base/public/calISchedulingSupport.idl | 47 + comm/calendar/base/public/calIStartupService.idl | 30 + comm/calendar/base/public/calIStatusObserver.idl | 60 + comm/calendar/base/public/calITimezone.idl | 43 + comm/calendar/base/public/calITimezoneDatabase.idl | 37 + comm/calendar/base/public/calITimezoneService.idl | 50 + comm/calendar/base/public/calITodo.idl | 72 + comm/calendar/base/public/calIWeekInfoService.idl | 50 + comm/calendar/base/public/moz.build | 56 + comm/calendar/base/src/CalAlarm.jsm | 693 ++ comm/calendar/base/src/CalAlarmMonitor.jsm | 233 + comm/calendar/base/src/CalAlarmService.jsm | 827 ++ comm/calendar/base/src/CalAttachment.jsm | 169 + comm/calendar/base/src/CalAttendee.jsm | 212 + comm/calendar/base/src/CalCalendarManager.jsm | 1076 +++ comm/calendar/base/src/CalDateTime.jsm | 202 + comm/calendar/base/src/CalDefaultACLManager.jsm | 97 + comm/calendar/base/src/CalDeletedItems.jsm | 200 + comm/calendar/base/src/CalDuration.jsm | 106 + comm/calendar/base/src/CalEvent.jsm | 225 + comm/calendar/base/src/CalFreeBusyService.jsm | 89 + comm/calendar/base/src/CalICSService.jsm | 604 ++ comm/calendar/base/src/CalIcsParser.jsm | 334 + comm/calendar/base/src/CalIcsSerializer.jsm | 77 + comm/calendar/base/src/CalItipItem.jsm | 212 + comm/calendar/base/src/CalMetronome.jsm | 142 + comm/calendar/base/src/CalMimeConverter.jsm | 69 + comm/calendar/base/src/CalPeriod.jsm | 87 + comm/calendar/base/src/CalProtocolHandler.jsm | 63 + .../calendar/base/src/CalReadableStreamFactory.jsm | 314 + comm/calendar/base/src/CalRecurrenceDate.jsm | 122 + comm/calendar/base/src/CalRecurrenceInfo.jsm | 847 ++ comm/calendar/base/src/CalRecurrenceRule.jsm | 268 + comm/calendar/base/src/CalRelation.jsm | 125 + comm/calendar/base/src/CalStartupService.jsm | 124 + comm/calendar/base/src/CalTimezone.jsm | 77 + comm/calendar/base/src/CalTimezoneService.jsm | 228 + comm/calendar/base/src/CalTodo.jsm | 264 + comm/calendar/base/src/CalTransactionManager.jsm | 372 + comm/calendar/base/src/CalWeekInfoService.jsm | 113 + comm/calendar/base/src/TimezoneDatabase.cpp | 114 + comm/calendar/base/src/TimezoneDatabase.h | 20 + comm/calendar/base/src/calApplicationUtils.js | 47 + comm/calendar/base/src/calCachedCalendar.js | 957 ++ comm/calendar/base/src/calICSService-worker.js | 21 + comm/calendar/base/src/calInternalInterfaces.idl | 29 + comm/calendar/base/src/calItemBase.js | 1198 +++ comm/calendar/base/src/components.conf | 208 + comm/calendar/base/src/moz.build | 71 + .../base/themes/common/calendar-alarms.css | 83 + .../base/themes/common/calendar-attendees.css | 216 + .../base/themes/common/calendar-creation.css | 99 + .../base/themes/common/calendar-daypicker.css | 28 + .../themes/common/calendar-invitation-display.css | 13 + .../base/themes/common/calendar-item-summary.css | 77 + .../base/themes/common/calendar-itip-icons.svg | 122 + .../themes/common/calendar-occurrence-prompt.css | 67 + .../base/themes/common/calendar-occurrence.svg | 20 + .../base/themes/common/calendar-preferences.css | 76 + .../calendar/base/themes/common/calendar-print.css | 66 + .../common/calendar-providerUninstall-dialog.css | 13 + .../base/themes/common/calendar-task-tree.css | 132 + .../base/themes/common/calendar-task-view.css | 236 + .../base/themes/common/calendar-toolbar.css | 21 + .../base/themes/common/calendar-unifinder.css | 40 + .../calendar/base/themes/common/calendar-views.css | 1232 +++ comm/calendar/base/themes/common/calendar.css | 255 + .../base/themes/common/datetimepickers.css | 260 + .../common/dialogs/calendar-alarm-dialog.css | 112 + .../dialogs/calendar-event-dialog-attendees.css | 264 + .../common/dialogs/calendar-event-dialog.css | 677 ++ .../common/dialogs/calendar-ics-file-dialog.css | 107 + .../common/dialogs/calendar-invitations-dialog.css | 80 + .../dialogs/calendar-itip-identity-dialog.css | 11 + .../common/dialogs/calendar-properties-dialog.css | 89 + .../common/dialogs/calendar-summary-dialog.css | 47 + .../dialogs/calendar-timezone-highlighter.css | 142 + .../dialogs/calendar-uri-redirect-dialog.css | 13 + .../themes/common/dialogs/chooseCalendarDialog.css | 19 + .../images/calendar-event-dialog-attendees.png | Bin 0 -> 8696 bytes .../calendar-invitations-dialog-list-images.png | Bin 0 -> 2150 bytes .../themes/common/dialogs/images/chain-lock.svg | 6 + .../themes/common/dialogs/images/chain-unlock.svg | 6 + .../common/dialogs/images/link-image-bottom.svg | 6 + .../common/dialogs/images/link-image-top.svg | 7 + .../dialogs/images/statusbar-priority-high.svg | 8 + .../dialogs/images/statusbar-priority-low.svg | 6 + .../dialogs/images/statusbar-priority-normal.svg | 7 + .../calendar/base/themes/common/icons/alarm-no.svg | 6 + comm/calendar/base/themes/common/icons/alarm.svg | 6 + .../calendar/base/themes/common/icons/complete.svg | 6 + .../base/themes/common/icons/confidential.svg | 6 + comm/calendar/base/themes/common/icons/decline.svg | 6 + comm/calendar/base/themes/common/icons/edit.svg | 6 + comm/calendar/base/themes/common/icons/email.svg | 7 + comm/calendar/base/themes/common/icons/event.svg | 10 + comm/calendar/base/themes/common/icons/find.svg | 6 + .../calendar/base/themes/common/icons/freebusy.svg | 6 + comm/calendar/base/themes/common/icons/icon32.svg | 6 + .../calendar/base/themes/common/icons/imip-bar.svg | 6 + comm/calendar/base/themes/common/icons/locked.svg | 6 + .../base/themes/common/icons/low-priority.svg | 6 + .../calendar/base/themes/common/icons/newevent.svg | 10 + comm/calendar/base/themes/common/icons/newtask.svg | 6 + comm/calendar/base/themes/common/icons/pane.svg | 8 + .../calendar/base/themes/common/icons/priority.svg | 6 + comm/calendar/base/themes/common/icons/private.svg | 6 + .../themes/common/icons/recurrence-exception.svg | 6 + .../base/themes/common/icons/recurrence.svg | 6 + .../base/themes/common/icons/save-close.svg | 6 + comm/calendar/base/themes/common/icons/sort.svg | 6 + comm/calendar/base/themes/common/icons/status.svg | 10 + .../base/themes/common/icons/synchronize.svg | 6 + .../calendar/base/themes/common/icons/task-tab.svg | 6 + comm/calendar/base/themes/common/icons/task.svg | 6 + .../base/themes/common/icons/tentative.svg | 6 + .../base/themes/common/icons/timezones.svg | 6 + comm/calendar/base/themes/common/icons/today.svg | 9 + comm/calendar/base/themes/common/icons/warn.svg | 6 + .../base/themes/common/images/attendee-icons.png | Bin 0 -> 5592 bytes .../base/themes/common/images/event-continue.svg | 6 + .../base/themes/common/images/event-end.svg | 6 + .../base/themes/common/images/event-grippy.png | Bin 0 -> 98 bytes .../base/themes/common/images/event-start.svg | 6 + .../base/themes/common/images/timezone_map.png | Bin 0 -> 14423 bytes .../base/themes/common/images/timezones.png | Bin 0 -> 101805 bytes .../themes/common/images/todayButton-arrow.svg | 6 + .../base/themes/common/images/todo-complete.svg | 6 + comm/calendar/base/themes/common/images/todo.svg | 6 + comm/calendar/base/themes/common/imip.css | 76 + comm/calendar/base/themes/common/jar.inc.mn | 99 + comm/calendar/base/themes/common/publishDialog.css | 53 + comm/calendar/base/themes/common/today-pane.css | 433 + comm/calendar/base/themes/common/view-cycler.svg | 6 + .../common/widgets/calendar-invitation-panel.css | 135 + .../themes/common/widgets/calendar-minidate.css | 36 + .../themes/common/widgets/calendar-widgets.css | 388 + .../themes/common/widgets/images/drag-center.svg | 9 + .../themes/common/widgets/images/nav-arrow.svg | 6 + .../themes/common/widgets/images/nav-today.svg | 7 + .../common/widgets/images/view-navigation.svg | 6 + .../base/themes/common/widgets/minimonth.css | 385 + .../base/themes/linux/calendar-daypicker.css | 18 + .../base/themes/linux/calendar-task-tree.css | 13 + .../base/themes/linux/calendar-task-view.css | 35 + .../base/themes/linux/calendar-unifinder.css | 33 + comm/calendar/base/themes/linux/calendar-views.css | 9 + comm/calendar/base/themes/linux/calendar.css | 46 + .../themes/linux/dialogs/calendar-alarm-dialog.css | 5 + .../themes/linux/dialogs/calendar-event-dialog.css | 43 + .../linux/dialogs/calendar-invitations-dialog.css | 5 + comm/calendar/base/themes/linux/imip.css | 5 + comm/calendar/base/themes/linux/jar.mn | 19 + comm/calendar/base/themes/linux/moz.build | 6 + comm/calendar/base/themes/linux/today-pane.css | 29 + .../base/themes/linux/widgets/calendar-widgets.css | 22 + comm/calendar/base/themes/moz.build | 11 + .../base/themes/osx/calendar-daypicker.css | 18 + .../base/themes/osx/calendar-task-tree.css | 9 + .../base/themes/osx/calendar-task-view.css | 41 + .../base/themes/osx/calendar-unifinder.css | 22 + comm/calendar/base/themes/osx/calendar-views.css | 9 + comm/calendar/base/themes/osx/calendar.css | 69 + .../themes/osx/dialogs/calendar-alarm-dialog.css | 18 + .../themes/osx/dialogs/calendar-event-dialog.css | 27 + .../osx/dialogs/calendar-invitations-dialog.css | 5 + comm/calendar/base/themes/osx/imip.css | 10 + comm/calendar/base/themes/osx/jar.mn | 19 + comm/calendar/base/themes/osx/moz.build | 6 + comm/calendar/base/themes/osx/today-pane.css | 51 + .../base/themes/osx/widgets/calendar-widgets.css | 19 + .../base/themes/windows/calendar-daypicker.css | 18 + .../base/themes/windows/calendar-task-tree.css | 60 + .../base/themes/windows/calendar-task-view.css | 58 + .../base/themes/windows/calendar-unifinder.css | 26 + .../base/themes/windows/calendar-views.css | 16 + comm/calendar/base/themes/windows/calendar.css | 52 + .../windows/dialogs/calendar-alarm-dialog.css | 19 + .../windows/dialogs/calendar-event-dialog.css | 42 + .../dialogs/calendar-invitations-dialog.css | 5 + comm/calendar/base/themes/windows/imip.css | 5 + comm/calendar/base/themes/windows/jar.mn | 19 + comm/calendar/base/themes/windows/moz.build | 6 + comm/calendar/base/themes/windows/today-pane.css | 64 + .../themes/windows/widgets/calendar-widgets.css | 29 + comm/calendar/extract/CalExtractParser.jsm | 545 ++ comm/calendar/extract/CalExtractParserService.jsm | 308 + comm/calendar/extract/moz.build | 9 + comm/calendar/import-export/CalHtmlExport.jsm | 116 + comm/calendar/import-export/CalIcsImportExport.jsm | 55 + comm/calendar/import-export/calHtmlExport.html | 71 + comm/calendar/import-export/components.conf | 29 + comm/calendar/import-export/jar.mn | 7 + comm/calendar/import-export/moz.build | 18 + comm/calendar/itip/CalItipEmailTransport.jsm | 439 + comm/calendar/itip/CalItipMessageSender.jsm | 433 + comm/calendar/itip/CalItipOutgoingMessage.jsm | 93 + comm/calendar/itip/CalItipProtocolHandler.jsm | 118 + comm/calendar/itip/components.conf | 39 + comm/calendar/itip/moz.build | 18 + comm/calendar/locales/Makefile.in | 6 + comm/calendar/locales/all-locales | 66 + comm/calendar/locales/en-US/README.txt | 3 + .../en-US/calendar/calendar-context-menus.ftl | 11 + .../en-US/calendar/calendar-delete-prompt.ftl | 43 + .../en-US/calendar/calendar-editable-item.ftl | 42 + .../calendar/calendar-event-dialog-reminder.ftl | 12 + .../en-US/calendar/calendar-ics-file-dialog.ftl | 61 + .../en-US/calendar/calendar-invitation-panel.ftl | 137 + .../en-US/calendar/calendar-invitations-dialog.ftl | 12 + .../calendar/calendar-itip-identity-dialog.ftl | 11 + .../locales/en-US/calendar/calendar-print.ftl | 20 + .../en-US/calendar/calendar-recurrence-dialog.ftl | 11 + .../en-US/calendar/calendar-summary-dialog.ftl | 21 + .../calendar/calendar-uri-redirect-dialog.ftl | 15 + .../locales/en-US/calendar/calendar-widgets.ftl | 145 + .../locales/en-US/calendar/category-dialog.ftl | 8 + .../locales/en-US/calendar/preferences.ftl | 237 + .../chrome/calendar/calendar-alarms.properties | 39 + .../calendar-event-dialog-attendees.properties | 15 + .../chrome/calendar/calendar-event-dialog.dtd | 418 + .../calendar/calendar-event-dialog.properties | 541 ++ .../chrome/calendar/calendar-extract.properties | 294 + .../calendar/calendar-invitations-dialog.dtd | 13 + .../calendar-invitations-dialog.properties | 10 + .../chrome/calendar/calendar-occurrence-prompt.dtd | 7 + .../calendar/calendar-occurrence-prompt.properties | 53 + .../locales/en-US/chrome/calendar/calendar.dtd | 354 + .../en-US/chrome/calendar/calendar.properties | 696 ++ .../en-US/chrome/calendar/calendarCreation.dtd | 51 + .../chrome/calendar/calendarCreation.properties | 6 + .../en-US/chrome/calendar/categories.properties | 7 + .../en-US/chrome/calendar/dateFormat.properties | 146 + .../dialogs/calendar-event-dialog-reminder.dtd | 19 + .../locales/en-US/chrome/calendar/global.dtd | 21 + .../locales/en-US/chrome/calendar/menuOverlay.dtd | 46 + .../locales/en-US/chrome/calendar/migration.dtd | 9 + .../en-US/chrome/calendar/migration.properties | 11 + .../en-US/chrome/calendar/provider-uninstall.dtd | 12 + .../en-US/chrome/calendar/timezones.properties | 490 + .../en-US/chrome/lightning/lightning-toolbar.dtd | 25 + .../locales/en-US/chrome/lightning/lightning.dtd | 112 + .../en-US/chrome/lightning/lightning.properties | 165 + comm/calendar/locales/filter.py | 27 + comm/calendar/locales/jar.mn | 38 + comm/calendar/locales/l10n.ini | 12 + comm/calendar/locales/l10n.toml | 30 + comm/calendar/locales/moz.build | 6 + comm/calendar/locales/shipped-locales | 38 + comm/calendar/moz.build | 34 + comm/calendar/providers/caldav/CalDavCalendar.jsm | 2464 +++++ comm/calendar/providers/caldav/CalDavProvider.jsm | 426 + comm/calendar/providers/caldav/components.conf | 14 + .../providers/caldav/modules/CalDavRequest.jsm | 1211 +++ .../caldav/modules/CalDavRequestHandlers.jsm | 1091 +++ .../providers/caldav/modules/CalDavSession.jsm | 573 ++ .../providers/caldav/modules/CalDavUtils.jsm | 110 + comm/calendar/providers/caldav/moz.build | 25 + .../providers/caldav/public/calICalDavCalendar.idl | 20 + comm/calendar/providers/caldav/public/moz.build | 10 + .../providers/composite/CalCompositeCalendar.jsm | 426 + comm/calendar/providers/composite/components.conf | 14 + comm/calendar/providers/composite/moz.build | 12 + comm/calendar/providers/ics/CalICSCalendar.sys.mjs | 1235 +++ comm/calendar/providers/ics/CalICSProvider.jsm | 447 + comm/calendar/providers/ics/components.conf | 14 + comm/calendar/providers/ics/moz.build | 16 + .../providers/memory/CalMemoryCalendar.jsm | 538 ++ comm/calendar/providers/memory/components.conf | 14 + comm/calendar/providers/memory/moz.build | 12 + comm/calendar/providers/moz.build | 12 + .../storage/CalStorageCachedItemModel.jsm | 219 + .../providers/storage/CalStorageCalendar.jsm | 563 ++ .../providers/storage/CalStorageDatabase.jsm | 333 + .../providers/storage/CalStorageItemModel.jsm | 1374 +++ .../providers/storage/CalStorageMetaDataModel.jsm | 94 + .../providers/storage/CalStorageModelBase.jsm | 65 + .../providers/storage/CalStorageModelFactory.jsm | 52 + .../providers/storage/CalStorageOfflineModel.jsm | 54 + .../providers/storage/CalStorageStatements.jsm | 751 ++ .../providers/storage/calStorageHelpers.jsm | 121 + .../providers/storage/calStorageUpgrade.jsm | 1889 ++++ comm/calendar/providers/storage/components.conf | 14 + comm/calendar/providers/storage/moz.build | 28 + comm/calendar/test/.eslintrc.js | 74 + comm/calendar/test/CalDAVServer.jsm | 627 ++ comm/calendar/test/CalendarTestUtils.jsm | 1203 +++ comm/calendar/test/CalendarUtils.jsm | 87 + comm/calendar/test/ICSServer.jsm | 153 + comm/calendar/test/ItemEditingHelpers.jsm | 681 ++ comm/calendar/test/browser/browser.ini | 39 + .../test/browser/browser_basicFunctionality.js | 78 + .../test/browser/browser_calDAV_discovery.js | 241 + comm/calendar/test/browser/browser_calDAV_oAuth.js | 201 + comm/calendar/test/browser/browser_calendarList.js | 341 + .../test/browser/browser_calendarTelemetry.js | 119 + .../test/browser/browser_calendarUnifinder.js | 76 + .../calendar/test/browser/browser_dragEventItem.js | 414 + .../test/browser/browser_eventDisplay_dayView.js | 133 + .../browser/browser_eventDisplay_multiWeekView.js | 275 + .../test/browser/browser_eventDisplay_weekView.js | 151 + .../calendar/test/browser/browser_eventUndoRedo.js | 260 + comm/calendar/test/browser/browser_import.js | 285 + comm/calendar/test/browser/browser_localICS.js | 63 + comm/calendar/test/browser/browser_tabs.js | 26 + comm/calendar/test/browser/browser_taskDelete.js | 185 + comm/calendar/test/browser/browser_taskDisplay.js | 274 + comm/calendar/test/browser/browser_taskUndoRedo.js | 244 + comm/calendar/test/browser/browser_todayPane.js | 820 ++ .../test/browser/browser_todayPane_dragAndDrop.js | 262 + .../test/browser/browser_todayPane_visibility.js | 167 + comm/calendar/test/browser/contextMenu/browser.ini | 14 + .../test/browser/contextMenu/browser_edit.js | 187 + comm/calendar/test/browser/data/attachment.png | Bin 0 -> 82 bytes comm/calendar/test/browser/data/calendars.sjs | 126 + comm/calendar/test/browser/data/dns.sjs | 56 + comm/calendar/test/browser/data/event.ics | 10 + comm/calendar/test/browser/data/import.ics | 24 + comm/calendar/test/browser/data/principal.sjs | 39 + comm/calendar/test/browser/eventDialog/browser.ini | 27 + .../browser/eventDialog/browser_alarmDialog.js | 88 + .../test/browser/eventDialog/browser_attachMenu.js | 266 + .../browser/eventDialog/browser_attendeesDialog.js | 462 + .../eventDialog/browser_attendeesDialogAdd.js | 248 + .../eventDialog/browser_attendeesDialogNoEdit.js | 68 + .../eventDialog/browser_attendeesDialogRemove.js | 147 + .../eventDialog/browser_attendeesDialogUpdate.js | 140 + .../browser/eventDialog/browser_eventDialog.js | 399 + .../browser_eventDialogDescriptionEditor.js | 154 + .../eventDialog/browser_eventDialogEditButton.js | 223 + .../browser_eventDialogModificationPrompt.js | 160 + .../test/browser/eventDialog/browser_utf8.js | 56 + .../test/browser/eventDialog/data/guests.txt | 2 + comm/calendar/test/browser/eventDialog/head.js | 97 + comm/calendar/test/browser/head.js | 374 + comm/calendar/test/browser/invitations/browser.ini | 31 + .../invitations/browser_attachedPublishEvent.js | 72 + .../browser/invitations/browser_icsAttachment.js | 71 + .../browser/invitations/browser_identityPrompt.js | 144 + .../test/browser/invitations/browser_imipBar.js | 199 + .../browser/invitations/browser_imipBarCancel.js | 129 + .../browser/invitations/browser_imipBarEmail.js | 168 + .../invitations/browser_imipBarExceptionCancel.js | 137 + .../invitations/browser_imipBarExceptionOnly.js | 262 + .../invitations/browser_imipBarExceptions.js | 288 + .../browser/invitations/browser_imipBarRepeat.js | 218 + .../invitations/browser_imipBarRepeatCancel.js | 186 + .../invitations/browser_imipBarRepeatUpdates.js | 247 + .../browser/invitations/browser_imipBarUpdates.js | 223 + .../invitations/browser_invitationDisplayNew.js | 257 + .../browser/invitations/browser_unsupportedFreq.js | 107 + .../invitations/data/cancel-repeat-event.eml | 49 + .../invitations/data/cancel-single-event.eml | 78 + .../browser/invitations/data/exception-major.eml | 49 + .../browser/invitations/data/exception-minor.eml | 49 + .../invitations/data/meet-meeting-invite.eml | 384 + .../invitations/data/message-containing-event.eml | 44 + .../invitations/data/message-non-invite.eml | 115 + .../invitations/data/outlook-test-invite.eml | 102 + .../test/browser/invitations/data/repeat-event.eml | 49 + .../invitations/data/repeat-update-major.eml | 49 + .../invitations/data/repeat-update-minor.eml | 49 + .../test/browser/invitations/data/single-event.eml | 78 + .../invitations/data/teams-meeting-invite.eml | 167 + .../test/browser/invitations/data/update-major.eml | 78 + .../test/browser/invitations/data/update-minor.eml | 78 + comm/calendar/test/browser/invitations/head.js | 942 ++ comm/calendar/test/browser/preferences/browser.ini | 16 + .../preferences/browser_alarmDefaultValue.js | 176 + .../browser/preferences/browser_categoryColors.js | 90 + comm/calendar/test/browser/preferences/head.js | 64 + comm/calendar/test/browser/providers/browser.ini | 21 + .../providers/browser_caldavCalendar_cached.js | 64 + .../providers/browser_caldavCalendar_uncached.js | 61 + .../providers/browser_icsCalendar_cached.js | 73 + .../providers/browser_icsCalendar_uncached.js | 64 + .../browser/providers/browser_storageCalendar.js | 13 + comm/calendar/test/browser/providers/head.js | 402 + comm/calendar/test/browser/recurrence/browser.ini | 23 + .../test/browser/recurrence/browser_annual.js | 69 + .../test/browser/recurrence/browser_biweekly.js | 85 + .../test/browser/recurrence/browser_daily.js | 162 + .../browser/recurrence/browser_lastDayOfMonth.js | 112 + .../recurrence/browser_recurrenceNavigation.js | 138 + .../test/browser/recurrence/browser_rotated.ini | 24 + .../test/browser/recurrence/browser_weeklyN.js | 268 + .../test/browser/recurrence/browser_weeklyUntil.js | 175 + .../recurrence/browser_weeklyWithException.js | 264 + comm/calendar/test/browser/recurrence/head.js | 26 + comm/calendar/test/browser/timezones/browser.ini | 17 + .../test/browser/timezones/browser_minimonth.js | 215 + .../test/browser/timezones/browser_timezones.js | 867 ++ comm/calendar/test/browser/views/browser.ini | 32 + .../calendar/test/browser/views/browser_dayView.js | 185 + .../test/browser/views/browser_monthView.js | 86 + .../test/browser/views/browser_multiweekView.js | 88 + .../test/browser/views/browser_propertyChanges.js | 248 + .../test/browser/views/browser_taskView.js | 148 + .../test/browser/views/browser_viewSwitch.js | 138 + .../test/browser/views/browser_weekView.js | 81 + comm/calendar/test/browser/views/head.js | 13 + comm/calendar/test/moz.build | 30 + comm/calendar/test/unit/data/bug1790339.sql | 194 + comm/calendar/test/unit/data/import.ics | 24 + comm/calendar/test/unit/head.js | 337 + comm/calendar/test/unit/providers/head.js | 152 + .../unit/providers/test_caldavCalendar_cached.js | 201 + .../unit/providers/test_caldavCalendar_uncached.js | 96 + .../test/unit/providers/test_icsCalendar_cached.js | 53 + .../unit/providers/test_icsCalendar_uncached.js | 46 + .../test/unit/providers/test_storageCalendar.js | 17 + comm/calendar/test/unit/providers/xpcshell.ini | 11 + .../test/unit/test_CalendarFileImporter.js | 46 + comm/calendar/test/unit/test_alarm.js | 674 ++ comm/calendar/test/unit/test_alarmservice.js | 606 ++ comm/calendar/test/unit/test_alarmutils.js | 171 + comm/calendar/test/unit/test_attachment.js | 112 + comm/calendar/test/unit/test_attendee.js | 318 + comm/calendar/test/unit/test_auth_utils.js | 100 + comm/calendar/test/unit/test_bug1199942.js | 81 + comm/calendar/test/unit/test_bug1204255.js | 146 + comm/calendar/test/unit/test_bug1209399.js | 117 + comm/calendar/test/unit/test_bug1790339.js | 71 + comm/calendar/test/unit/test_bug272411.js | 15 + comm/calendar/test/unit/test_bug343792.js | 66 + comm/calendar/test/unit/test_bug350845.js | 43 + comm/calendar/test/unit/test_bug356207.js | 47 + comm/calendar/test/unit/test_bug485571.js | 99 + comm/calendar/test/unit/test_bug486186.js | 21 + comm/calendar/test/unit/test_bug494140.js | 57 + comm/calendar/test/unit/test_bug523860.js | 15 + comm/calendar/test/unit/test_bug653924.js | 20 + comm/calendar/test/unit/test_bug668222.js | 28 + comm/calendar/test/unit/test_bug759324.js | 74 + comm/calendar/test/unit/test_calIteratorUtils.js | 38 + comm/calendar/test/unit/test_calStorageHelpers.js | 23 + comm/calendar/test/unit/test_caldav_requests.js | 970 ++ comm/calendar/test/unit/test_calmgr.js | 411 + .../test/unit/test_calreadablestreamfactory.js | 195 + comm/calendar/test/unit/test_data_bags.js | 151 + comm/calendar/test/unit/test_datetime.js | 99 + .../test/unit/test_datetime_before_1970.js | 31 + comm/calendar/test/unit/test_datetimeformatter.js | 604 ++ comm/calendar/test/unit/test_deleted_items.js | 106 + comm/calendar/test/unit/test_duration.js | 10 + comm/calendar/test/unit/test_email_utils.js | 265 + comm/calendar/test/unit/test_extract.js | 225 + comm/calendar/test/unit/test_extract_parser.js | 160 + .../test/unit/test_extract_parser_parse.js | 1317 +++ .../test/unit/test_extract_parser_service.js | 96 + .../test/unit/test_extract_parser_tokenize.js | 367 + comm/calendar/test/unit/test_filter.js | 406 + comm/calendar/test/unit/test_filter_mixin.js | 1083 +++ comm/calendar/test/unit/test_filter_tree_view.js | 451 + comm/calendar/test/unit/test_freebusy.js | 88 + comm/calendar/test/unit/test_freebusy_service.js | 201 + comm/calendar/test/unit/test_hashedarray.js | 210 + comm/calendar/test/unit/test_ics.js | 235 + comm/calendar/test/unit/test_ics_parser.js | 220 + comm/calendar/test/unit/test_ics_service.js | 289 + comm/calendar/test/unit/test_imip.js | 47 + comm/calendar/test/unit/test_invitationutils.js | 1654 ++++ comm/calendar/test/unit/test_items.js | 465 + .../calendar/test/unit/test_itip_message_sender.js | 358 + comm/calendar/test/unit/test_itip_utils.js | 831 ++ comm/calendar/test/unit/test_l10n_utils.js | 99 + comm/calendar/test/unit/test_lenient_parsing.js | 41 + comm/calendar/test/unit/test_providers.js | 426 + comm/calendar/test/unit/test_recur.js | 1361 +++ comm/calendar/test/unit/test_recurrence_utils.js | 371 + comm/calendar/test/unit/test_relation.js | 133 + comm/calendar/test/unit/test_rfc3339_parser.js | 188 + comm/calendar/test/unit/test_startup_service.js | 46 + comm/calendar/test/unit/test_storage.js | 85 + comm/calendar/test/unit/test_storage_connection.js | 127 + comm/calendar/test/unit/test_storage_get_items.js | 338 + comm/calendar/test/unit/test_timezone.js | 89 + comm/calendar/test/unit/test_timezone_changes.js | 93 + .../calendar/test/unit/test_timezone_definition.js | 32 + .../calendar/test/unit/test_transaction_manager.js | 431 + comm/calendar/test/unit/test_unifinder_utils.js | 137 + comm/calendar/test/unit/test_utils.js | 185 + comm/calendar/test/unit/test_view_utils.js | 127 + comm/calendar/test/unit/test_webcal.js | 44 + comm/calendar/test/unit/test_weekinfo_service.js | 33 + comm/calendar/test/unit/xpcshell.ini | 82 + 666 files changed, 156900 insertions(+) create mode 100644 comm/calendar/.prettierrc create mode 100644 comm/calendar/base/calendar.js create mode 100644 comm/calendar/base/content/calendar-base-view.js create mode 100644 comm/calendar/base/content/calendar-chrome-startup.js create mode 100644 comm/calendar/base/content/calendar-clipboard.js create mode 100644 comm/calendar/base/content/calendar-command-controller.js create mode 100644 comm/calendar/base/content/calendar-commands.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-context-menus-and-tooltips.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-day-label.js create mode 100644 comm/calendar/base/content/calendar-dnd-listener.js create mode 100644 comm/calendar/base/content/calendar-editable-item.js create mode 100644 comm/calendar/base/content/calendar-extract.js create mode 100644 comm/calendar/base/content/calendar-invitation-display.js create mode 100644 comm/calendar/base/content/calendar-invitations-manager.js create mode 100644 comm/calendar/base/content/calendar-keys.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-management.js create mode 100644 comm/calendar/base/content/calendar-menu-events-tasks.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-menus.js create mode 100644 comm/calendar/base/content/calendar-migration.js create mode 100644 comm/calendar/base/content/calendar-modes.js create mode 100644 comm/calendar/base/content/calendar-month-view.js create mode 100644 comm/calendar/base/content/calendar-multiday-view.js create mode 100644 comm/calendar/base/content/calendar-print.js create mode 100644 comm/calendar/base/content/calendar-status-bar.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-statusbar.js create mode 100644 comm/calendar/base/content/calendar-tab-panels.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-tabs.js create mode 100644 comm/calendar/base/content/calendar-task-tree-utils.js create mode 100644 comm/calendar/base/content/calendar-task-tree-view.js create mode 100644 comm/calendar/base/content/calendar-task-tree.js create mode 100644 comm/calendar/base/content/calendar-task-view.js create mode 100644 comm/calendar/base/content/calendar-today-pane.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-ui-utils.js create mode 100644 comm/calendar/base/content/calendar-unifinder.js create mode 100644 comm/calendar/base/content/calendar-view-menu.inc.xhtml create mode 100644 comm/calendar/base/content/calendar-views-utils.js create mode 100644 comm/calendar/base/content/calendar-views.js create mode 100644 comm/calendar/base/content/dialogs/calendar-alarm-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-alarm-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-conflicts-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-conflicts-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-creation.js create mode 100644 comm/calendar/base/content/dialogs/calendar-creation.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-dialog-utils.js create mode 100644 comm/calendar/base/content/dialogs/calendar-error-prompt.js create mode 100644 comm/calendar/base/content/dialogs/calendar-error-prompt.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-attendees.js create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-attendees.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-recurrence.js create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-recurrence.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-reminder.js create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-reminder.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-timezone.js create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog-timezone.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-event-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-ics-file-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-ics-file-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-identity-utils.js create mode 100644 comm/calendar/base/content/dialogs/calendar-invitations-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-invitations-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-itip-identity-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-itip-identity-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-migration-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-migration-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-occurrence-prompt.js create mode 100644 comm/calendar/base/content/dialogs/calendar-occurrence-prompt.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-properties-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-properties-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-providerUninstall-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-providerUninstall-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-summary-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-summary-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/calendar-uri-redirect-dialog.js create mode 100644 comm/calendar/base/content/dialogs/calendar-uri-redirect-dialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/chooseCalendarDialog.js create mode 100644 comm/calendar/base/content/dialogs/chooseCalendarDialog.xhtml create mode 100644 comm/calendar/base/content/dialogs/publishDialog.js create mode 100644 comm/calendar/base/content/dialogs/publishDialog.xhtml create mode 100644 comm/calendar/base/content/imip-bar-overlay.inc.xhtml create mode 100644 comm/calendar/base/content/imip-bar.js create mode 100644 comm/calendar/base/content/import-export.js create mode 100644 comm/calendar/base/content/item-editing/calendar-item-editing.js create mode 100644 comm/calendar/base/content/item-editing/calendar-item-iframe.js create mode 100644 comm/calendar/base/content/item-editing/calendar-item-iframe.xhtml create mode 100644 comm/calendar/base/content/item-editing/calendar-item-panel.inc.xhtml create mode 100644 comm/calendar/base/content/item-editing/calendar-item-panel.js create mode 100644 comm/calendar/base/content/item-editing/calendar-item-toolbar.inc.xhtml create mode 100644 comm/calendar/base/content/item-editing/calendar-task-editing.js create mode 100644 comm/calendar/base/content/preferences/alarms.inc.xhtml create mode 100644 comm/calendar/base/content/preferences/alarms.js create mode 100644 comm/calendar/base/content/preferences/calendar-preferences.inc.xhtml create mode 100644 comm/calendar/base/content/preferences/calendar-preferences.js create mode 100644 comm/calendar/base/content/preferences/categories.inc.xhtml create mode 100644 comm/calendar/base/content/preferences/categories.js create mode 100644 comm/calendar/base/content/preferences/editCategory.js create mode 100644 comm/calendar/base/content/preferences/editCategory.xhtml create mode 100644 comm/calendar/base/content/preferences/general.inc.xhtml create mode 100644 comm/calendar/base/content/preferences/general.js create mode 100644 comm/calendar/base/content/preferences/notifications.js create mode 100644 comm/calendar/base/content/preferences/views.inc.xhtml create mode 100644 comm/calendar/base/content/preferences/views.js create mode 100644 comm/calendar/base/content/printing-template.html create mode 100644 comm/calendar/base/content/publish.js create mode 100644 comm/calendar/base/content/sound.wav create mode 100644 comm/calendar/base/content/today-pane-agenda.js create mode 100644 comm/calendar/base/content/today-pane.js create mode 100644 comm/calendar/base/content/widgets/calendar-alarm-widget.js create mode 100644 comm/calendar/base/content/widgets/calendar-dnd-widgets.js create mode 100644 comm/calendar/base/content/widgets/calendar-filter-tree-view.js create mode 100644 comm/calendar/base/content/widgets/calendar-filter.js create mode 100644 comm/calendar/base/content/widgets/calendar-invitation-panel.js create mode 100644 comm/calendar/base/content/widgets/calendar-invitation-panel.xhtml create mode 100644 comm/calendar/base/content/widgets/calendar-item-summary.js create mode 100644 comm/calendar/base/content/widgets/calendar-minidate.js create mode 100644 comm/calendar/base/content/widgets/calendar-minidate.xhtml create mode 100644 comm/calendar/base/content/widgets/calendar-minimonth.js create mode 100644 comm/calendar/base/content/widgets/calendar-modebox.js create mode 100644 comm/calendar/base/content/widgets/calendar-notifications-setting.js create mode 100644 comm/calendar/base/content/widgets/datetimepickers.js create mode 100644 comm/calendar/base/content/widgets/mouseoverPreviews.js create mode 100644 comm/calendar/base/jar.mn create mode 100644 comm/calendar/base/modules/Ical.jsm create mode 100644 comm/calendar/base/modules/calCalendarDeactivator.jsm create mode 100644 comm/calendar/base/modules/calExtract.jsm create mode 100644 comm/calendar/base/modules/calHashedArray.jsm create mode 100644 comm/calendar/base/modules/calRecurrenceUtils.jsm create mode 100644 comm/calendar/base/modules/calUtils.jsm create mode 100644 comm/calendar/base/modules/moz.build create mode 100644 comm/calendar/base/modules/utils/calACLUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calAlarmUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calAuthUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calCategoryUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calDataUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calDateTimeFormatter.jsm create mode 100644 comm/calendar/base/modules/utils/calDateTimeUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calEmailUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calInvitationUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calItemUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calIteratorUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calItipUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calL10NUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calPrintUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calProviderDetectionUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calProviderUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calUnifinderUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calViewUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calWindowUtils.jsm create mode 100644 comm/calendar/base/modules/utils/calXMLUtils.jsm create mode 100644 comm/calendar/base/moz.build create mode 100644 comm/calendar/base/public/calIAlarm.idl create mode 100644 comm/calendar/base/public/calIAlarmService.idl create mode 100644 comm/calendar/base/public/calIAttachment.idl create mode 100644 comm/calendar/base/public/calIAttendee.idl create mode 100644 comm/calendar/base/public/calICalendar.idl create mode 100644 comm/calendar/base/public/calICalendarACLManager.idl create mode 100644 comm/calendar/base/public/calICalendarManager.idl create mode 100644 comm/calendar/base/public/calICalendarProvider.idl create mode 100644 comm/calendar/base/public/calICalendarView.idl create mode 100644 comm/calendar/base/public/calICalendarViewController.idl create mode 100644 comm/calendar/base/public/calIChangeLog.idl create mode 100644 comm/calendar/base/public/calIDateTime.idl create mode 100644 comm/calendar/base/public/calIDeletedItems.idl create mode 100644 comm/calendar/base/public/calIDuration.idl create mode 100644 comm/calendar/base/public/calIErrors.idl create mode 100644 comm/calendar/base/public/calIEvent.idl create mode 100644 comm/calendar/base/public/calIFreeBusyProvider.idl create mode 100644 comm/calendar/base/public/calIICSService.idl create mode 100644 comm/calendar/base/public/calIIcsParser.idl create mode 100644 comm/calendar/base/public/calIIcsSerializer.idl create mode 100644 comm/calendar/base/public/calIImportExport.idl create mode 100644 comm/calendar/base/public/calIItemBase.idl create mode 100644 comm/calendar/base/public/calIItipItem.idl create mode 100644 comm/calendar/base/public/calIItipTransport.idl create mode 100644 comm/calendar/base/public/calIOperation.idl create mode 100644 comm/calendar/base/public/calIPeriod.idl create mode 100644 comm/calendar/base/public/calIRecurrenceDate.idl create mode 100644 comm/calendar/base/public/calIRecurrenceInfo.idl create mode 100644 comm/calendar/base/public/calIRecurrenceItem.idl create mode 100644 comm/calendar/base/public/calIRecurrenceRule.idl create mode 100644 comm/calendar/base/public/calIRelation.idl create mode 100644 comm/calendar/base/public/calISchedulingSupport.idl create mode 100644 comm/calendar/base/public/calIStartupService.idl create mode 100644 comm/calendar/base/public/calIStatusObserver.idl create mode 100644 comm/calendar/base/public/calITimezone.idl create mode 100644 comm/calendar/base/public/calITimezoneDatabase.idl create mode 100644 comm/calendar/base/public/calITimezoneService.idl create mode 100644 comm/calendar/base/public/calITodo.idl create mode 100644 comm/calendar/base/public/calIWeekInfoService.idl create mode 100644 comm/calendar/base/public/moz.build create mode 100644 comm/calendar/base/src/CalAlarm.jsm create mode 100644 comm/calendar/base/src/CalAlarmMonitor.jsm create mode 100644 comm/calendar/base/src/CalAlarmService.jsm create mode 100644 comm/calendar/base/src/CalAttachment.jsm create mode 100644 comm/calendar/base/src/CalAttendee.jsm create mode 100644 comm/calendar/base/src/CalCalendarManager.jsm create mode 100644 comm/calendar/base/src/CalDateTime.jsm create mode 100644 comm/calendar/base/src/CalDefaultACLManager.jsm create mode 100644 comm/calendar/base/src/CalDeletedItems.jsm create mode 100644 comm/calendar/base/src/CalDuration.jsm create mode 100644 comm/calendar/base/src/CalEvent.jsm create mode 100644 comm/calendar/base/src/CalFreeBusyService.jsm create mode 100644 comm/calendar/base/src/CalICSService.jsm create mode 100644 comm/calendar/base/src/CalIcsParser.jsm create mode 100644 comm/calendar/base/src/CalIcsSerializer.jsm create mode 100644 comm/calendar/base/src/CalItipItem.jsm create mode 100644 comm/calendar/base/src/CalMetronome.jsm create mode 100644 comm/calendar/base/src/CalMimeConverter.jsm create mode 100644 comm/calendar/base/src/CalPeriod.jsm create mode 100644 comm/calendar/base/src/CalProtocolHandler.jsm create mode 100644 comm/calendar/base/src/CalReadableStreamFactory.jsm create mode 100644 comm/calendar/base/src/CalRecurrenceDate.jsm create mode 100644 comm/calendar/base/src/CalRecurrenceInfo.jsm create mode 100644 comm/calendar/base/src/CalRecurrenceRule.jsm create mode 100644 comm/calendar/base/src/CalRelation.jsm create mode 100644 comm/calendar/base/src/CalStartupService.jsm create mode 100644 comm/calendar/base/src/CalTimezone.jsm create mode 100644 comm/calendar/base/src/CalTimezoneService.jsm create mode 100644 comm/calendar/base/src/CalTodo.jsm create mode 100644 comm/calendar/base/src/CalTransactionManager.jsm create mode 100644 comm/calendar/base/src/CalWeekInfoService.jsm create mode 100644 comm/calendar/base/src/TimezoneDatabase.cpp create mode 100644 comm/calendar/base/src/TimezoneDatabase.h create mode 100644 comm/calendar/base/src/calApplicationUtils.js create mode 100644 comm/calendar/base/src/calCachedCalendar.js create mode 100644 comm/calendar/base/src/calICSService-worker.js create mode 100644 comm/calendar/base/src/calInternalInterfaces.idl create mode 100644 comm/calendar/base/src/calItemBase.js create mode 100644 comm/calendar/base/src/components.conf create mode 100644 comm/calendar/base/src/moz.build create mode 100644 comm/calendar/base/themes/common/calendar-alarms.css create mode 100644 comm/calendar/base/themes/common/calendar-attendees.css create mode 100644 comm/calendar/base/themes/common/calendar-creation.css create mode 100644 comm/calendar/base/themes/common/calendar-daypicker.css create mode 100644 comm/calendar/base/themes/common/calendar-invitation-display.css create mode 100644 comm/calendar/base/themes/common/calendar-item-summary.css create mode 100644 comm/calendar/base/themes/common/calendar-itip-icons.svg create mode 100644 comm/calendar/base/themes/common/calendar-occurrence-prompt.css create mode 100644 comm/calendar/base/themes/common/calendar-occurrence.svg create mode 100644 comm/calendar/base/themes/common/calendar-preferences.css create mode 100644 comm/calendar/base/themes/common/calendar-print.css create mode 100644 comm/calendar/base/themes/common/calendar-providerUninstall-dialog.css create mode 100644 comm/calendar/base/themes/common/calendar-task-tree.css create mode 100644 comm/calendar/base/themes/common/calendar-task-view.css create mode 100644 comm/calendar/base/themes/common/calendar-toolbar.css create mode 100644 comm/calendar/base/themes/common/calendar-unifinder.css create mode 100644 comm/calendar/base/themes/common/calendar-views.css create mode 100644 comm/calendar/base/themes/common/calendar.css create mode 100644 comm/calendar/base/themes/common/datetimepickers.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-alarm-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-event-dialog-attendees.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-event-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-ics-file-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-invitations-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-itip-identity-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-properties-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-summary-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-timezone-highlighter.css create mode 100644 comm/calendar/base/themes/common/dialogs/calendar-uri-redirect-dialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/chooseCalendarDialog.css create mode 100644 comm/calendar/base/themes/common/dialogs/images/calendar-event-dialog-attendees.png create mode 100644 comm/calendar/base/themes/common/dialogs/images/calendar-invitations-dialog-list-images.png create mode 100644 comm/calendar/base/themes/common/dialogs/images/chain-lock.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/chain-unlock.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/link-image-bottom.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/link-image-top.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/statusbar-priority-high.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/statusbar-priority-low.svg create mode 100644 comm/calendar/base/themes/common/dialogs/images/statusbar-priority-normal.svg create mode 100644 comm/calendar/base/themes/common/icons/alarm-no.svg create mode 100644 comm/calendar/base/themes/common/icons/alarm.svg create mode 100644 comm/calendar/base/themes/common/icons/complete.svg create mode 100644 comm/calendar/base/themes/common/icons/confidential.svg create mode 100644 comm/calendar/base/themes/common/icons/decline.svg create mode 100644 comm/calendar/base/themes/common/icons/edit.svg create mode 100644 comm/calendar/base/themes/common/icons/email.svg create mode 100644 comm/calendar/base/themes/common/icons/event.svg create mode 100644 comm/calendar/base/themes/common/icons/find.svg create mode 100644 comm/calendar/base/themes/common/icons/freebusy.svg create mode 100644 comm/calendar/base/themes/common/icons/icon32.svg create mode 100644 comm/calendar/base/themes/common/icons/imip-bar.svg create mode 100644 comm/calendar/base/themes/common/icons/locked.svg create mode 100644 comm/calendar/base/themes/common/icons/low-priority.svg create mode 100644 comm/calendar/base/themes/common/icons/newevent.svg create mode 100644 comm/calendar/base/themes/common/icons/newtask.svg create mode 100644 comm/calendar/base/themes/common/icons/pane.svg create mode 100644 comm/calendar/base/themes/common/icons/priority.svg create mode 100644 comm/calendar/base/themes/common/icons/private.svg create mode 100644 comm/calendar/base/themes/common/icons/recurrence-exception.svg create mode 100644 comm/calendar/base/themes/common/icons/recurrence.svg create mode 100644 comm/calendar/base/themes/common/icons/save-close.svg create mode 100644 comm/calendar/base/themes/common/icons/sort.svg create mode 100644 comm/calendar/base/themes/common/icons/status.svg create mode 100644 comm/calendar/base/themes/common/icons/synchronize.svg create mode 100644 comm/calendar/base/themes/common/icons/task-tab.svg create mode 100644 comm/calendar/base/themes/common/icons/task.svg create mode 100644 comm/calendar/base/themes/common/icons/tentative.svg create mode 100644 comm/calendar/base/themes/common/icons/timezones.svg create mode 100644 comm/calendar/base/themes/common/icons/today.svg create mode 100644 comm/calendar/base/themes/common/icons/warn.svg create mode 100644 comm/calendar/base/themes/common/images/attendee-icons.png create mode 100644 comm/calendar/base/themes/common/images/event-continue.svg create mode 100644 comm/calendar/base/themes/common/images/event-end.svg create mode 100644 comm/calendar/base/themes/common/images/event-grippy.png create mode 100644 comm/calendar/base/themes/common/images/event-start.svg create mode 100644 comm/calendar/base/themes/common/images/timezone_map.png create mode 100644 comm/calendar/base/themes/common/images/timezones.png create mode 100644 comm/calendar/base/themes/common/images/todayButton-arrow.svg create mode 100644 comm/calendar/base/themes/common/images/todo-complete.svg create mode 100644 comm/calendar/base/themes/common/images/todo.svg create mode 100644 comm/calendar/base/themes/common/imip.css create mode 100644 comm/calendar/base/themes/common/jar.inc.mn create mode 100644 comm/calendar/base/themes/common/publishDialog.css create mode 100644 comm/calendar/base/themes/common/today-pane.css create mode 100644 comm/calendar/base/themes/common/view-cycler.svg create mode 100644 comm/calendar/base/themes/common/widgets/calendar-invitation-panel.css create mode 100644 comm/calendar/base/themes/common/widgets/calendar-minidate.css create mode 100644 comm/calendar/base/themes/common/widgets/calendar-widgets.css create mode 100644 comm/calendar/base/themes/common/widgets/images/drag-center.svg create mode 100644 comm/calendar/base/themes/common/widgets/images/nav-arrow.svg create mode 100644 comm/calendar/base/themes/common/widgets/images/nav-today.svg create mode 100644 comm/calendar/base/themes/common/widgets/images/view-navigation.svg create mode 100644 comm/calendar/base/themes/common/widgets/minimonth.css create mode 100644 comm/calendar/base/themes/linux/calendar-daypicker.css create mode 100644 comm/calendar/base/themes/linux/calendar-task-tree.css create mode 100644 comm/calendar/base/themes/linux/calendar-task-view.css create mode 100644 comm/calendar/base/themes/linux/calendar-unifinder.css create mode 100644 comm/calendar/base/themes/linux/calendar-views.css create mode 100644 comm/calendar/base/themes/linux/calendar.css create mode 100644 comm/calendar/base/themes/linux/dialogs/calendar-alarm-dialog.css create mode 100644 comm/calendar/base/themes/linux/dialogs/calendar-event-dialog.css create mode 100644 comm/calendar/base/themes/linux/dialogs/calendar-invitations-dialog.css create mode 100644 comm/calendar/base/themes/linux/imip.css create mode 100644 comm/calendar/base/themes/linux/jar.mn create mode 100644 comm/calendar/base/themes/linux/moz.build create mode 100644 comm/calendar/base/themes/linux/today-pane.css create mode 100644 comm/calendar/base/themes/linux/widgets/calendar-widgets.css create mode 100644 comm/calendar/base/themes/moz.build create mode 100644 comm/calendar/base/themes/osx/calendar-daypicker.css create mode 100644 comm/calendar/base/themes/osx/calendar-task-tree.css create mode 100644 comm/calendar/base/themes/osx/calendar-task-view.css create mode 100644 comm/calendar/base/themes/osx/calendar-unifinder.css create mode 100644 comm/calendar/base/themes/osx/calendar-views.css create mode 100644 comm/calendar/base/themes/osx/calendar.css create mode 100644 comm/calendar/base/themes/osx/dialogs/calendar-alarm-dialog.css create mode 100644 comm/calendar/base/themes/osx/dialogs/calendar-event-dialog.css create mode 100644 comm/calendar/base/themes/osx/dialogs/calendar-invitations-dialog.css create mode 100644 comm/calendar/base/themes/osx/imip.css create mode 100644 comm/calendar/base/themes/osx/jar.mn create mode 100644 comm/calendar/base/themes/osx/moz.build create mode 100644 comm/calendar/base/themes/osx/today-pane.css create mode 100644 comm/calendar/base/themes/osx/widgets/calendar-widgets.css create mode 100644 comm/calendar/base/themes/windows/calendar-daypicker.css create mode 100644 comm/calendar/base/themes/windows/calendar-task-tree.css create mode 100644 comm/calendar/base/themes/windows/calendar-task-view.css create mode 100644 comm/calendar/base/themes/windows/calendar-unifinder.css create mode 100644 comm/calendar/base/themes/windows/calendar-views.css create mode 100644 comm/calendar/base/themes/windows/calendar.css create mode 100644 comm/calendar/base/themes/windows/dialogs/calendar-alarm-dialog.css create mode 100644 comm/calendar/base/themes/windows/dialogs/calendar-event-dialog.css create mode 100644 comm/calendar/base/themes/windows/dialogs/calendar-invitations-dialog.css create mode 100644 comm/calendar/base/themes/windows/imip.css create mode 100644 comm/calendar/base/themes/windows/jar.mn create mode 100644 comm/calendar/base/themes/windows/moz.build create mode 100644 comm/calendar/base/themes/windows/today-pane.css create mode 100644 comm/calendar/base/themes/windows/widgets/calendar-widgets.css create mode 100644 comm/calendar/extract/CalExtractParser.jsm create mode 100644 comm/calendar/extract/CalExtractParserService.jsm create mode 100644 comm/calendar/extract/moz.build create mode 100644 comm/calendar/import-export/CalHtmlExport.jsm create mode 100644 comm/calendar/import-export/CalIcsImportExport.jsm create mode 100644 comm/calendar/import-export/calHtmlExport.html create mode 100644 comm/calendar/import-export/components.conf create mode 100644 comm/calendar/import-export/jar.mn create mode 100644 comm/calendar/import-export/moz.build create mode 100644 comm/calendar/itip/CalItipEmailTransport.jsm create mode 100644 comm/calendar/itip/CalItipMessageSender.jsm create mode 100644 comm/calendar/itip/CalItipOutgoingMessage.jsm create mode 100644 comm/calendar/itip/CalItipProtocolHandler.jsm create mode 100644 comm/calendar/itip/components.conf create mode 100644 comm/calendar/itip/moz.build create mode 100644 comm/calendar/locales/Makefile.in create mode 100644 comm/calendar/locales/all-locales create mode 100644 comm/calendar/locales/en-US/README.txt create mode 100644 comm/calendar/locales/en-US/calendar/calendar-context-menus.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-delete-prompt.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-editable-item.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-event-dialog-reminder.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-ics-file-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-invitation-panel.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-invitations-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-itip-identity-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-print.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-recurrence-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-summary-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-uri-redirect-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/calendar-widgets.ftl create mode 100644 comm/calendar/locales/en-US/calendar/category-dialog.ftl create mode 100644 comm/calendar/locales/en-US/calendar/preferences.ftl create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-alarms.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-event-dialog-attendees.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-extract.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-invitations-dialog.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-invitations-dialog.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-occurrence-prompt.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar-occurrence-prompt.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendar.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendarCreation.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/calendarCreation.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/categories.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/dateFormat.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/dialogs/calendar-event-dialog-reminder.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/global.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/menuOverlay.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/migration.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/migration.properties create mode 100644 comm/calendar/locales/en-US/chrome/calendar/provider-uninstall.dtd create mode 100644 comm/calendar/locales/en-US/chrome/calendar/timezones.properties create mode 100644 comm/calendar/locales/en-US/chrome/lightning/lightning-toolbar.dtd create mode 100644 comm/calendar/locales/en-US/chrome/lightning/lightning.dtd create mode 100644 comm/calendar/locales/en-US/chrome/lightning/lightning.properties create mode 100644 comm/calendar/locales/filter.py create mode 100644 comm/calendar/locales/jar.mn create mode 100644 comm/calendar/locales/l10n.ini create mode 100644 comm/calendar/locales/l10n.toml create mode 100644 comm/calendar/locales/moz.build create mode 100644 comm/calendar/locales/shipped-locales create mode 100644 comm/calendar/moz.build create mode 100644 comm/calendar/providers/caldav/CalDavCalendar.jsm create mode 100644 comm/calendar/providers/caldav/CalDavProvider.jsm create mode 100644 comm/calendar/providers/caldav/components.conf create mode 100644 comm/calendar/providers/caldav/modules/CalDavRequest.jsm create mode 100644 comm/calendar/providers/caldav/modules/CalDavRequestHandlers.jsm create mode 100644 comm/calendar/providers/caldav/modules/CalDavSession.jsm create mode 100644 comm/calendar/providers/caldav/modules/CalDavUtils.jsm create mode 100644 comm/calendar/providers/caldav/moz.build create mode 100644 comm/calendar/providers/caldav/public/calICalDavCalendar.idl create mode 100644 comm/calendar/providers/caldav/public/moz.build create mode 100644 comm/calendar/providers/composite/CalCompositeCalendar.jsm create mode 100644 comm/calendar/providers/composite/components.conf create mode 100644 comm/calendar/providers/composite/moz.build create mode 100644 comm/calendar/providers/ics/CalICSCalendar.sys.mjs create mode 100644 comm/calendar/providers/ics/CalICSProvider.jsm create mode 100644 comm/calendar/providers/ics/components.conf create mode 100644 comm/calendar/providers/ics/moz.build create mode 100644 comm/calendar/providers/memory/CalMemoryCalendar.jsm create mode 100644 comm/calendar/providers/memory/components.conf create mode 100644 comm/calendar/providers/memory/moz.build create mode 100644 comm/calendar/providers/moz.build create mode 100644 comm/calendar/providers/storage/CalStorageCachedItemModel.jsm create mode 100644 comm/calendar/providers/storage/CalStorageCalendar.jsm create mode 100644 comm/calendar/providers/storage/CalStorageDatabase.jsm create mode 100644 comm/calendar/providers/storage/CalStorageItemModel.jsm create mode 100644 comm/calendar/providers/storage/CalStorageMetaDataModel.jsm create mode 100644 comm/calendar/providers/storage/CalStorageModelBase.jsm create mode 100644 comm/calendar/providers/storage/CalStorageModelFactory.jsm create mode 100644 comm/calendar/providers/storage/CalStorageOfflineModel.jsm create mode 100644 comm/calendar/providers/storage/CalStorageStatements.jsm create mode 100644 comm/calendar/providers/storage/calStorageHelpers.jsm create mode 100644 comm/calendar/providers/storage/calStorageUpgrade.jsm create mode 100644 comm/calendar/providers/storage/components.conf create mode 100644 comm/calendar/providers/storage/moz.build create mode 100644 comm/calendar/test/.eslintrc.js create mode 100644 comm/calendar/test/CalDAVServer.jsm create mode 100644 comm/calendar/test/CalendarTestUtils.jsm create mode 100644 comm/calendar/test/CalendarUtils.jsm create mode 100644 comm/calendar/test/ICSServer.jsm create mode 100644 comm/calendar/test/ItemEditingHelpers.jsm create mode 100644 comm/calendar/test/browser/browser.ini create mode 100644 comm/calendar/test/browser/browser_basicFunctionality.js create mode 100644 comm/calendar/test/browser/browser_calDAV_discovery.js create mode 100644 comm/calendar/test/browser/browser_calDAV_oAuth.js create mode 100644 comm/calendar/test/browser/browser_calendarList.js create mode 100644 comm/calendar/test/browser/browser_calendarTelemetry.js create mode 100644 comm/calendar/test/browser/browser_calendarUnifinder.js create mode 100644 comm/calendar/test/browser/browser_dragEventItem.js create mode 100644 comm/calendar/test/browser/browser_eventDisplay_dayView.js create mode 100644 comm/calendar/test/browser/browser_eventDisplay_multiWeekView.js create mode 100644 comm/calendar/test/browser/browser_eventDisplay_weekView.js create mode 100644 comm/calendar/test/browser/browser_eventUndoRedo.js create mode 100644 comm/calendar/test/browser/browser_import.js create mode 100644 comm/calendar/test/browser/browser_localICS.js create mode 100644 comm/calendar/test/browser/browser_tabs.js create mode 100644 comm/calendar/test/browser/browser_taskDelete.js create mode 100644 comm/calendar/test/browser/browser_taskDisplay.js create mode 100644 comm/calendar/test/browser/browser_taskUndoRedo.js create mode 100644 comm/calendar/test/browser/browser_todayPane.js create mode 100644 comm/calendar/test/browser/browser_todayPane_dragAndDrop.js create mode 100644 comm/calendar/test/browser/browser_todayPane_visibility.js create mode 100644 comm/calendar/test/browser/contextMenu/browser.ini create mode 100644 comm/calendar/test/browser/contextMenu/browser_edit.js create mode 100644 comm/calendar/test/browser/data/attachment.png create mode 100644 comm/calendar/test/browser/data/calendars.sjs create mode 100644 comm/calendar/test/browser/data/dns.sjs create mode 100644 comm/calendar/test/browser/data/event.ics create mode 100644 comm/calendar/test/browser/data/import.ics create mode 100644 comm/calendar/test/browser/data/principal.sjs create mode 100644 comm/calendar/test/browser/eventDialog/browser.ini create mode 100644 comm/calendar/test/browser/eventDialog/browser_alarmDialog.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attachMenu.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attendeesDialog.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attendeesDialogAdd.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attendeesDialogNoEdit.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attendeesDialogRemove.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_attendeesDialogUpdate.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_eventDialog.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_eventDialogDescriptionEditor.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_eventDialogEditButton.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_eventDialogModificationPrompt.js create mode 100644 comm/calendar/test/browser/eventDialog/browser_utf8.js create mode 100644 comm/calendar/test/browser/eventDialog/data/guests.txt create mode 100644 comm/calendar/test/browser/eventDialog/head.js create mode 100644 comm/calendar/test/browser/head.js create mode 100644 comm/calendar/test/browser/invitations/browser.ini create mode 100644 comm/calendar/test/browser/invitations/browser_attachedPublishEvent.js create mode 100644 comm/calendar/test/browser/invitations/browser_icsAttachment.js create mode 100644 comm/calendar/test/browser/invitations/browser_identityPrompt.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBar.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarEmail.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptionCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptionOnly.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptions.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeat.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeatCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeatUpdates.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarUpdates.js create mode 100644 comm/calendar/test/browser/invitations/browser_invitationDisplayNew.js create mode 100644 comm/calendar/test/browser/invitations/browser_unsupportedFreq.js create mode 100644 comm/calendar/test/browser/invitations/data/cancel-repeat-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/cancel-single-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/exception-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/exception-minor.eml create mode 100644 comm/calendar/test/browser/invitations/data/meet-meeting-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/message-containing-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/message-non-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/outlook-test-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-update-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-update-minor.eml create mode 100644 comm/calendar/test/browser/invitations/data/single-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/teams-meeting-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/update-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/update-minor.eml create mode 100644 comm/calendar/test/browser/invitations/head.js create mode 100644 comm/calendar/test/browser/preferences/browser.ini create mode 100644 comm/calendar/test/browser/preferences/browser_alarmDefaultValue.js create mode 100644 comm/calendar/test/browser/preferences/browser_categoryColors.js create mode 100644 comm/calendar/test/browser/preferences/head.js create mode 100644 comm/calendar/test/browser/providers/browser.ini create mode 100644 comm/calendar/test/browser/providers/browser_caldavCalendar_cached.js create mode 100644 comm/calendar/test/browser/providers/browser_caldavCalendar_uncached.js create mode 100644 comm/calendar/test/browser/providers/browser_icsCalendar_cached.js create mode 100644 comm/calendar/test/browser/providers/browser_icsCalendar_uncached.js create mode 100644 comm/calendar/test/browser/providers/browser_storageCalendar.js create mode 100644 comm/calendar/test/browser/providers/head.js create mode 100644 comm/calendar/test/browser/recurrence/browser.ini create mode 100644 comm/calendar/test/browser/recurrence/browser_annual.js create mode 100644 comm/calendar/test/browser/recurrence/browser_biweekly.js create mode 100644 comm/calendar/test/browser/recurrence/browser_daily.js create mode 100644 comm/calendar/test/browser/recurrence/browser_lastDayOfMonth.js create mode 100644 comm/calendar/test/browser/recurrence/browser_recurrenceNavigation.js create mode 100644 comm/calendar/test/browser/recurrence/browser_rotated.ini create mode 100644 comm/calendar/test/browser/recurrence/browser_weeklyN.js create mode 100644 comm/calendar/test/browser/recurrence/browser_weeklyUntil.js create mode 100644 comm/calendar/test/browser/recurrence/browser_weeklyWithException.js create mode 100644 comm/calendar/test/browser/recurrence/head.js create mode 100644 comm/calendar/test/browser/timezones/browser.ini create mode 100644 comm/calendar/test/browser/timezones/browser_minimonth.js create mode 100644 comm/calendar/test/browser/timezones/browser_timezones.js create mode 100644 comm/calendar/test/browser/views/browser.ini create mode 100644 comm/calendar/test/browser/views/browser_dayView.js create mode 100644 comm/calendar/test/browser/views/browser_monthView.js create mode 100644 comm/calendar/test/browser/views/browser_multiweekView.js create mode 100644 comm/calendar/test/browser/views/browser_propertyChanges.js create mode 100644 comm/calendar/test/browser/views/browser_taskView.js create mode 100644 comm/calendar/test/browser/views/browser_viewSwitch.js create mode 100644 comm/calendar/test/browser/views/browser_weekView.js create mode 100644 comm/calendar/test/browser/views/head.js create mode 100644 comm/calendar/test/moz.build create mode 100644 comm/calendar/test/unit/data/bug1790339.sql create mode 100644 comm/calendar/test/unit/data/import.ics create mode 100644 comm/calendar/test/unit/head.js create mode 100644 comm/calendar/test/unit/providers/head.js create mode 100644 comm/calendar/test/unit/providers/test_caldavCalendar_cached.js create mode 100644 comm/calendar/test/unit/providers/test_caldavCalendar_uncached.js create mode 100644 comm/calendar/test/unit/providers/test_icsCalendar_cached.js create mode 100644 comm/calendar/test/unit/providers/test_icsCalendar_uncached.js create mode 100644 comm/calendar/test/unit/providers/test_storageCalendar.js create mode 100644 comm/calendar/test/unit/providers/xpcshell.ini create mode 100644 comm/calendar/test/unit/test_CalendarFileImporter.js create mode 100644 comm/calendar/test/unit/test_alarm.js create mode 100644 comm/calendar/test/unit/test_alarmservice.js create mode 100644 comm/calendar/test/unit/test_alarmutils.js create mode 100644 comm/calendar/test/unit/test_attachment.js create mode 100644 comm/calendar/test/unit/test_attendee.js create mode 100644 comm/calendar/test/unit/test_auth_utils.js create mode 100644 comm/calendar/test/unit/test_bug1199942.js create mode 100644 comm/calendar/test/unit/test_bug1204255.js create mode 100644 comm/calendar/test/unit/test_bug1209399.js create mode 100644 comm/calendar/test/unit/test_bug1790339.js create mode 100644 comm/calendar/test/unit/test_bug272411.js create mode 100644 comm/calendar/test/unit/test_bug343792.js create mode 100644 comm/calendar/test/unit/test_bug350845.js create mode 100644 comm/calendar/test/unit/test_bug356207.js create mode 100644 comm/calendar/test/unit/test_bug485571.js create mode 100644 comm/calendar/test/unit/test_bug486186.js create mode 100644 comm/calendar/test/unit/test_bug494140.js create mode 100644 comm/calendar/test/unit/test_bug523860.js create mode 100644 comm/calendar/test/unit/test_bug653924.js create mode 100644 comm/calendar/test/unit/test_bug668222.js create mode 100644 comm/calendar/test/unit/test_bug759324.js create mode 100644 comm/calendar/test/unit/test_calIteratorUtils.js create mode 100644 comm/calendar/test/unit/test_calStorageHelpers.js create mode 100644 comm/calendar/test/unit/test_caldav_requests.js create mode 100644 comm/calendar/test/unit/test_calmgr.js create mode 100644 comm/calendar/test/unit/test_calreadablestreamfactory.js create mode 100644 comm/calendar/test/unit/test_data_bags.js create mode 100644 comm/calendar/test/unit/test_datetime.js create mode 100644 comm/calendar/test/unit/test_datetime_before_1970.js create mode 100644 comm/calendar/test/unit/test_datetimeformatter.js create mode 100644 comm/calendar/test/unit/test_deleted_items.js create mode 100644 comm/calendar/test/unit/test_duration.js create mode 100644 comm/calendar/test/unit/test_email_utils.js create mode 100644 comm/calendar/test/unit/test_extract.js create mode 100644 comm/calendar/test/unit/test_extract_parser.js create mode 100644 comm/calendar/test/unit/test_extract_parser_parse.js create mode 100644 comm/calendar/test/unit/test_extract_parser_service.js create mode 100644 comm/calendar/test/unit/test_extract_parser_tokenize.js create mode 100644 comm/calendar/test/unit/test_filter.js create mode 100644 comm/calendar/test/unit/test_filter_mixin.js create mode 100644 comm/calendar/test/unit/test_filter_tree_view.js create mode 100644 comm/calendar/test/unit/test_freebusy.js create mode 100644 comm/calendar/test/unit/test_freebusy_service.js create mode 100644 comm/calendar/test/unit/test_hashedarray.js create mode 100644 comm/calendar/test/unit/test_ics.js create mode 100644 comm/calendar/test/unit/test_ics_parser.js create mode 100644 comm/calendar/test/unit/test_ics_service.js create mode 100644 comm/calendar/test/unit/test_imip.js create mode 100644 comm/calendar/test/unit/test_invitationutils.js create mode 100644 comm/calendar/test/unit/test_items.js create mode 100644 comm/calendar/test/unit/test_itip_message_sender.js create mode 100644 comm/calendar/test/unit/test_itip_utils.js create mode 100644 comm/calendar/test/unit/test_l10n_utils.js create mode 100644 comm/calendar/test/unit/test_lenient_parsing.js create mode 100644 comm/calendar/test/unit/test_providers.js create mode 100644 comm/calendar/test/unit/test_recur.js create mode 100644 comm/calendar/test/unit/test_recurrence_utils.js create mode 100644 comm/calendar/test/unit/test_relation.js create mode 100644 comm/calendar/test/unit/test_rfc3339_parser.js create mode 100644 comm/calendar/test/unit/test_startup_service.js create mode 100644 comm/calendar/test/unit/test_storage.js create mode 100644 comm/calendar/test/unit/test_storage_connection.js create mode 100644 comm/calendar/test/unit/test_storage_get_items.js create mode 100644 comm/calendar/test/unit/test_timezone.js create mode 100644 comm/calendar/test/unit/test_timezone_changes.js create mode 100644 comm/calendar/test/unit/test_timezone_definition.js create mode 100644 comm/calendar/test/unit/test_transaction_manager.js create mode 100644 comm/calendar/test/unit/test_unifinder_utils.js create mode 100644 comm/calendar/test/unit/test_utils.js create mode 100644 comm/calendar/test/unit/test_view_utils.js create mode 100644 comm/calendar/test/unit/test_webcal.js create mode 100644 comm/calendar/test/unit/test_weekinfo_service.js create mode 100644 comm/calendar/test/unit/xpcshell.ini (limited to 'comm/calendar') diff --git a/comm/calendar/.prettierrc b/comm/calendar/.prettierrc new file mode 100644 index 0000000000..0bce966680 --- /dev/null +++ b/comm/calendar/.prettierrc @@ -0,0 +1,7 @@ +{ + "arrowParens": "avoid", + "endOfLine": "lf", + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/comm/calendar/base/calendar.js b/comm/calendar/base/calendar.js new file mode 100644 index 0000000000..57afc317cc --- /dev/null +++ b/comm/calendar/base/calendar.js @@ -0,0 +1,175 @@ +#filter dumbComments emptyLines substitution + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This file contains all of the default preference values for Calendar. + +// Turns on basic calendar logging. +pref("calendar.debug.log", false); +// Turns on verbose calendar logging. +pref("calendar.debug.log.verbose", false); + +// general settings +pref("calendar.date.format", 0); +pref("calendar.event.defaultlength", 60); +pref("calendar.task.defaultstart", "none"); +pref("calendar.task.defaultstartoffset", 0); +pref("calendar.task.defaultstartoffsetunits", "minutes"); +pref("calendar.task.defaultdue", "none"); +pref("calendar.task.defaultdueoffset", 60); +pref("calendar.task.defaultdueoffsetunits", "minutes"); + +// default transparency (free-busy status) of standard and all-day events +pref("calendar.events.defaultTransparency.allday.transparent", true); +pref("calendar.events.defaultTransparency.standard.transparent", false); + +// Make "Edit" the default action for events. +pref("calendar.events.defaultActionEdit", false); + +// Number of days in Today Pane agenda +pref("calendar.agenda.days", 14); + +// alarm settings +pref("calendar.alarms.show", true); +pref("calendar.alarms.showmissed", true); +pref("calendar.alarms.playsound", true); +pref("calendar.alarms.soundType", 0); +pref("calendar.alarms.soundURL", "chrome://calendar/content/sound.wav"); +pref("calendar.alarms.defaultsnoozelength", 5); +pref("calendar.alarms.indicator.show", true); +pref("calendar.alarms.indicator.totaltime", 3600); + +// default alarm settings for new event +pref("calendar.alarms.onforevents", 0); +pref("calendar.alarms.eventalarmlen", 15); +pref("calendar.alarms.eventalarmunit", "minutes"); + +// default alarm settings for new task +pref("calendar.alarms.onfortodos", 0); +pref("calendar.alarms.todoalarmlen", 15); +pref("calendar.alarms.todoalarmunit", "minutes"); + +pref("calendar.alarms.loglevel", "Warn"); + +// The default timeouts to show notifications for calendar items. The value +// should be in the form of "-PT1D,PT2M,END:-PT3M", which means to show +// notifications at: 1 day before the start, 2 minutes after the start, 3 +// minutes before the end. +pref("calendar.notifications.times", ""); + +// open invitations autorefresh settings +pref("calendar.invitations.autorefresh.enabled", true); +pref("calendar.invitations.autorefresh.timeout", 3); + +// whether "notify" is checked by default when creating new events/todos with attendees +pref("calendar.itip.notify", true); + +// whether "Separate invitation per attendee" is checked by default +pref("calendar.itip.separateInvitationPerAttendee", false); + +// whether the organizer propagates replies of attendees to all attendees +pref("calendar.itip.notify-replies", false); + +// whether email invitation updates are send out to all attendees if (only) adding a new attendee +pref("calendar.itip.updateInvitationForNewAttendeesOnly", false); + +//whether changes in email invitation updates should be displayed +pref("calendar.itip.displayInvitationChanges", true); + +//whether for delegated invitations a delegatee's replies will be send also to delegator(s) +pref("calendar.itip.notifyDelegatorOnReply", true); + +// whether to prefix the subject field for email invitation invites or updates. +pref("calendar.itip.useInvitationSubjectPrefixes", true); + +// whether separate invitation actions to more separate buttons or integrate into few buttons +pref("calendar.itip.separateInvitationButtons", true); + +// Whether to show the imip bar. +pref("calendar.itip.showImipBar", true); + +// Whether to always expand the iMIP details, instead of collapsing them. +pref("calendar.itip.imipDetailsOpen", true); + +// Temporary pref for using the new invitation display instead of the old one. +pref("calendar.itip.newInvitationDisplay", false); + +// whether CalDAV (experimental) scheduling is enabled or not. +pref("calendar.caldav.sched.enabled", false); + +// 0=Sunday, 1=Monday, 2=Tuesday, etc. One day we might want to move this to +// a locale specific file. +pref("calendar.week.start", 0); +pref("calendar.weeks.inview", 4); +pref("calendar.previousweeks.inview", 0); + +// Show week number in minimonth and multiweek/month views +pref("calendar.view-minimonth.showWeekNumber", true); + +// Default days off +pref("calendar.week.d0sundaysoff", true); +pref("calendar.week.d1mondaysoff", false); +pref("calendar.week.d2tuesdaysoff", false); +pref("calendar.week.d3wednesdaysoff", false); +pref("calendar.week.d4thursdaysoff", false); +pref("calendar.week.d5fridaysoff", false); +pref("calendar.week.d6saturdaysoff", true); + +// start and end work hour for day and week views +pref("calendar.view.daystarthour", 8); +pref("calendar.view.dayendhour", 17); + +// number of visible hours for day and week views +pref("calendar.view.visiblehours", 9); + +// If true, mouse scrolling via shift+wheel will be enabled +pref("calendar.view.mousescroll", true); + +// Do not set this! If it's not there, then we guess the system timezone +//pref("calendar.timezone.local", ""); + +// Recent timezone list +pref("calendar.timezone.recent", "[]"); + +// categories settings +// XXX One day we might want to move this to a locale specific file +// and include a list of locale specific default categories +pref("calendar.categories.names", ""); + +// Disable use of worker threads. Restart needed. +pref("calendar.threading.disabled", false); + +// The maximum time in microseconds that a cal.iterate.forEach event can take (soft limit). +pref("calendar.threading.latency ", 250); + +// Enable support for multiple realms on one server with the payoff that you +// will get multiple password dialogs (one for each calendar) +pref("calendar.network.multirealm", false); + +// Disable hiding the label on todayPane button +pref("calendar.view.showTodayPaneStatusLabel", true); + +// Maximum number of iterations allowed when searching for the next matching +// occurrence of a repeating item in calFilter +pref("calendar.filter.maxiterations", 50); + +// Edit events and tasks in a tab rather than a window. +pref("calendar.item.editInTab", false); + +// Always use the currently selected calendar as target for paste operations +pref("calendar.paste.intoSelectedCalendar", false); + +pref("calendar.baseview.loglevel", "Warn"); + +// Enables the prompt when deleting from the item views or trees. +pref("calendar.item.promptDelete", true); + +// Enables the new extract service. +pref("calendar.extract.service.enabled", false); + +// Number of days to display in the invite attendees interface. +pref("calendar.view.attendees.visibleDays", 16); +// Only full days are displayed the invite attendees interface. +pref("calendar.view.attendees.showOnlyWholeDays", false); diff --git a/comm/calendar/base/content/calendar-base-view.js b/comm/calendar/base/content/calendar-base-view.js new file mode 100644 index 0000000000..317770b984 --- /dev/null +++ b/comm/calendar/base/content/calendar-base-view.js @@ -0,0 +1,647 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global cal, calendarNavigationBar, CalendarFilteredViewMixin, calFilterProperties, currentView, + gCurrentMode, MozElements, MozXULElement, Services, toggleOrientation */ + +"use strict"; + +// Wrap in a block to prevent leaking to window scope. +{ + const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + + /** + * Calendar observer for calendar view elements. Used in CalendarBaseView class. + * + * @implements {calIObserver} + * @implements {calICompositeObserver} + * @implements {calIAlarmServiceObserver} + */ + class CalendarViewObserver { + /** + * Constructor for CalendarViewObserver. + * + * @param {CalendarBaseView} calendarView - A calendar view. + */ + constructor(calendarView) { + this.calView = calendarView.calICalendarView; + } + + QueryInterface = ChromeUtils.generateQI(["calIAlarmServiceObserver"]); + + // calIAlarmServiceObserver + + onAlarm(alarmItem) { + this.calView.flashAlarm(alarmItem, false); + } + + onNotification(item) {} + + onRemoveAlarmsByItem(item) { + // Stop the flashing for the item. + this.calView.flashAlarm(item, true); + } + + onRemoveAlarmsByCalendar(calendar) { + // Stop the flashing for all items of this calendar. + for (const key in this.calView.mFlashingEvents) { + const item = this.calView.mFlashingEvents[key]; + if (item.calendar.id == calendar.id) { + this.calView.flashAlarm(item, true); + } + } + } + + onAlarmsLoaded(calendar) {} + + // End calIAlarmServiceObserver + } + + /** + * Abstract base class for calendar view elements (day, week, multiweek, month). + * + * @implements {calICalendarView} + * @abstract + */ + class CalendarBaseView extends CalendarFilteredViewMixin(MozXULElement) { + /** + * Whether the view has been initialized. + * + * @type {boolean} + */ + #isInitialized = false; + + connectedCallback() { + if (this.delayConnectedCallback() || this.hasConnected) { + return; + } + this.hasConnected = true; + + // For some unknown reason, `console.createInstance` isn't available when + // `ensureInitialized` runs. + this.mLog = console.createInstance({ + prefix: `calendar.baseview (${this.constructor.name})`, + maxLogLevel: "Warn", + maxLogLevelPref: "calendar.baseview.loglevel", + }); + + this.mSelectedItems = []; + } + + ensureInitialized() { + if (this.#isInitialized) { + return; + } + this.#isInitialized = true; + + this.calICalendarView = this.getCustomInterfaceCallback(Ci.calICalendarView); + + this.addEventListener("move", event => { + this.moveView(event.detail); + }); + + this.addEventListener("keypress", event => { + switch (event.key) { + case "PageUp": + this.moveView(-1); + break; + case "PageDown": + this.moveView(1); + break; + } + }); + + this.addEventListener("wheel", event => { + const pixelThreshold = 150; + + if (event.shiftKey && Services.prefs.getBoolPref("calendar.view.mousescroll", true)) { + let deltaView = 0; + if (event.deltaMode == event.DOM_DELTA_LINE) { + if (event.deltaY != 0) { + deltaView = event.deltaY < 0 ? -1 : 1; + } + } else if (event.deltaMode == event.DOM_DELTA_PIXEL) { + this.mPixelScrollDelta += event.deltaY; + if (this.mPixelScrollDelta > pixelThreshold) { + deltaView = 1; + this.mPixelScrollDelta = 0; + } else if (this.mPixelScrollDelta < -pixelThreshold) { + deltaView = -1; + this.mPixelScrollDelta = 0; + } + } + + if (deltaView != 0) { + this.moveView(deltaView); + } + event.preventDefault(); + } + }); + + this.addEventListener("MozRotateGesture", event => { + // Threshold for the minimum and maximum angle we should accept + // rotation for. 90 degrees minimum is most logical, but 45 degrees + // allows you to rotate with one hand. + const MIN_ROTATE_ANGLE = 45; + const MAX_ROTATE_ANGLE = 180; + + const absval = Math.abs(event.delta); + if (this.supportsRotation && absval >= MIN_ROTATE_ANGLE && absval < MAX_ROTATE_ANGLE) { + toggleOrientation(); + event.preventDefault(); + } + }); + + this.addEventListener("MozMagnifyGestureStart", event => { + this.mMagnifyAmount = 0; + }); + + this.addEventListener("MozMagnifyGestureUpdate", event => { + // Threshold as to how much magnification causes the zoom to happen. + const THRESHOLD = 30; + + if (this.supportsZoom) { + this.mMagnifyAmount += event.delta; + + if (this.mMagnifyAmount > THRESHOLD) { + this.zoomOut(); + this.mMagnifyAmount = 0; + } else if (this.mMagnifyAmount < -THRESHOLD) { + this.zoomIn(); + this.mMagnifyAmount = 0; + } + event.preventDefault(); + } + }); + + this.addEventListener("MozSwipeGesture", event => { + if ( + (event.direction == SimpleGestureEvent.DIRECTION_UP && !this.rotated) || + (event.direction == SimpleGestureEvent.DIRECTION_LEFT && this.rotated) + ) { + this.moveView(-1); + } else if ( + (event.direction == SimpleGestureEvent.DIRECTION_DOWN && !this.rotated) || + (event.direction == SimpleGestureEvent.DIRECTION_RIGHT && this.rotated) + ) { + this.moveView(1); + } + }); + + this.mRangeStartDate = null; + this.mRangeEndDate = null; + + this.mWorkdaysOnly = false; + + this.mController = null; + + this.mStartDate = null; + this.mEndDate = null; + + this.mTasksInView = false; + this.mShowCompleted = false; + + this.mDisplayDaysOff = true; + this.mDaysOffArray = [0, 6]; + + this.mTimezone = null; + this.mFlashingEvents = {}; + + this.mDropShadowsLength = null; + + this.mShadowOffset = null; + this.mDropShadows = null; + + this.mMagnifyAmount = 0; + this.mPixelScrollDelta = 0; + + this.mViewStart = null; + this.mViewEnd = null; + + this.mToggleStatus = 0; + + this.mToggleStatusFlag = { + WorkdaysOnly: 1, + TasksInView: 2, + ShowCompleted: 4, + }; + + this.mTimezoneObserver = { + observe: () => { + this.timezone = cal.dtz.defaultTimezone; + this.refreshView(); + + this.updateTimeIndicatorPosition(); + }, + }; + + this.mPrefObserver = { + calView: this.calICalendarView, + + observe(subj, topic, pref) { + this.calView.handlePreference(subj, topic, pref); + }, + }; + + this.mObserver = new CalendarViewObserver(this); + + const isChecked = id => document.getElementById(id).getAttribute("checked") == "true"; + + this.workdaysOnly = isChecked("calendar_toggle_workdays_only_command"); + this.tasksInView = isChecked("calendar_toggle_tasks_in_view_command"); + this.rotated = isChecked("calendar_toggle_orientation_command"); + this.showCompleted = isChecked("calendar_toggle_show_completed_in_view_command"); + + this.mTimezone = cal.dtz.defaultTimezone; + const alarmService = Cc["@mozilla.org/calendar/alarm-service;1"].getService( + Ci.calIAlarmService + ); + + alarmService.addObserver(this.mObserver); + + this.setAttribute("type", this.type); + + window.addEventListener("viewresize", event => { + if (gCurrentMode == "calendar" && this.isVisible()) { + this.onResize(); + } + }); + + // Add a preference observer to monitor changes. + Services.prefs.addObserver("calendar.", this.mPrefObserver); + Services.obs.addObserver(this.mTimezoneObserver, "defaultTimezoneChanged"); + + this.updateDaysOffPrefs(); + this.updateTimeIndicatorPosition(); + + // Remove observers on window unload. + window.addEventListener( + "unload", + () => { + alarmService.removeObserver(this.mObserver); + + Services.prefs.removeObserver("calendar.", this.mPrefObserver); + Services.obs.removeObserver(this.mTimezoneObserver, "defaultTimezoneChanged"); + }, + { once: true } + ); + } + + /** + * Handle resizing by adjusting the view to the new size. + * + * @param {calICalendarView} [calViewElem] - A calendar view element. + */ + onResize() { + // Child classes should provide the implementation. + throw new Error(this.constructor.name + ".onResize not implemented"); + } + + /** + * Whether the view has been initialized. + * + * @returns {boolean} - True if the view has been initialized, otherwise + * false. + */ + get isInitialized() { + return this.#isInitialized; + } + + get type() { + const typelist = this.id.split("-"); + return typelist[0]; + } + + set rotated(rotated) { + this.setAttribute("orient", rotated ? "horizontal" : "vertical"); + this.toggleAttribute("rotated", rotated); + } + + get rotated() { + return this.getAttribute("orient") == "horizontal"; + } + + get supportsRotation() { + return false; + } + + set displayDaysOff(displayDaysOff) { + this.mDisplayDaysOff = displayDaysOff; + } + + get displayDaysOff() { + return this.mDisplayDaysOff; + } + + set controller(controller) { + this.mController = controller; + } + + get controller() { + return this.mController; + } + + set daysOffArray(daysOffArray) { + this.mDaysOffArray = daysOffArray; + } + + get daysOffArray() { + return this.mDaysOffArray; + } + + set tasksInView(tasksInView) { + this.mTasksInView = tasksInView; + this.updateItemType(); + } + + get tasksInView() { + return this.mTasksInView; + } + + set showCompleted(showCompleted) { + this.mShowCompleted = showCompleted; + this.updateItemType(); + } + + get showCompleted() { + return this.mShowCompleted; + } + + set timezone(timezone) { + this.mTimezone = timezone; + } + + get timezone() { + return this.mTimezone; + } + + set workdaysOnly(workdaysOnly) { + this.mWorkdaysOnly = workdaysOnly; + } + + get workdaysOnly() { + return this.mWorkdaysOnly; + } + + get supportsWorkdaysOnly() { + return true; + } + + get supportsZoom() { + return false; + } + + get selectionObserver() { + return this.mSelectionObserver; + } + + get startDay() { + return this.startDate; + } + + get endDay() { + return this.endDate; + } + + get supportDisjointDates() { + return false; + } + + get hasDisjointDates() { + return false; + } + + set rangeStartDate(startDate) { + this.mRangeStartDate = startDate; + } + + get rangeStartDate() { + return this.mRangeStartDate; + } + + set rangeEndDate(endDate) { + this.mRangeEndDate = endDate; + } + + get rangeEndDate() { + return this.mRangeEndDate; + } + + get observerID() { + return "base-view-observer"; + } + + // The end date that should be used for getItems and similar queries. + get queryEndDate() { + if (!this.endDate) { + return null; + } + const end = this.endDate.clone(); + end.day += 1; + end.isDate = true; + return end; + } + + /** + * Return a date object representing the current day. + * + * @returns {calIDateTime} A date object. + */ + today() { + const date = cal.dtz.jsDateToDateTime(new Date()).getInTimezone(this.mTimezone); + date.isDate = true; + return date; + } + + /** + * Return whether this view is currently active and visible in the UI. + * + * @returns {boolean} + */ + isVisible() { + return this == currentView(); + } + + /** + * Set the view's item type based on the `tasksInView` and `showCompleted` properties. + */ + updateItemType() { + if (!this.mTasksInView) { + this.itemType = Ci.calICalendar.ITEM_FILTER_TYPE_EVENT; + return; + } + + let type = Ci.calICalendar.ITEM_FILTER_TYPE_ALL; + type |= this.mShowCompleted + ? Ci.calICalendar.ITEM_FILTER_COMPLETED_ALL + : Ci.calICalendar.ITEM_FILTER_COMPLETED_NO; + this.itemType = type; + } + + // CalendarFilteredViewMixin implementation (clearItems and removeItemsFromCalendar + // are implemented in subclasses). + + addItems(items) { + for (let item of items) { + this.doAddItem(item); + } + } + + removeItems(items) { + for (let item of items) { + this.doRemoveItem(item); + } + } + + // End of CalendarFilteredViewMixin implementation. + + /** + * Create and fire an event. + * + * @param {string} eventName - Name of the event. + * @param {object} eventDetail - The details to add to the event. + */ + fireEvent(eventName, eventDetail) { + this.dispatchEvent( + new CustomEvent(eventName, { bubbles: true, cancelable: false, detail: eventDetail }) + ); + } + + /** + * A preference handler typically called by a preferences observer when a preference + * changes. Handles common preferences while other preferences are handled in subclasses. + * + * @param {object} subject - A subject, a prefs object. + * @param {string} topic - A topic. + * @param {string} preference - A preference that has changed. + */ + handleCommonPreference(subject, topic, preference) { + switch (preference) { + case "calendar.week.d0sundaysoff": + case "calendar.week.d1mondaysoff": + case "calendar.week.d2tuesdaysoff": + case "calendar.week.d3wednesdaysoff": + case "calendar.week.d4thursdaysoff": + case "calendar.week.d5fridaysoff": + case "calendar.week.d6saturdaysoff": + this.updateDaysOffPrefs(); + break; + case "calendar.alarms.indicator.show": + case "calendar.date.format": + case "calendar.view.showLocation": + // Break here to ensure the view is refreshed. + break; + default: + return; + } + this.refreshView(); + } + + /** + * Check preferences and update which days are days off. + */ + updateDaysOffPrefs() { + const prefix = "calendar.week."; + const daysOffPrefs = [ + [0, "d0sundaysoff", "true"], + [1, "d1mondaysoff", "false"], + [2, "d2tuesdaysoff", "false"], + [3, "d3wednesdaysoff", "false"], + [4, "d4thursdaysoff", "false"], + [5, "d5fridaysoff", "false"], + [6, "d6saturdaysoff", "true"], + ]; + const filterDaysOff = ([number, name, defaultValue]) => + Services.prefs.getBoolPref(prefix + name, defaultValue); + + this.daysOffArray = daysOffPrefs.filter(filterDaysOff).map(pref => pref[0]); + } + + /** + * Adjust the position of this view's indicator of the current time, if any. + */ + updateTimeIndicatorPosition() {} + + /** + * Refresh the view. + */ + refreshView() { + if (!this.startDay || !this.endDay) { + // Don't refresh if we're not initialized. + return; + } + this.goToDay(this.selectedDay); + } + + handlePreference(subject, topic, pref) { + // Do nothing by default. + } + + flashAlarm(alarmItem, stop) { + // Do nothing by default. + } + + // calICalendarView Methods + + /** + * @note This is overridden in each of the built-in calendar views. + * It's only left here in case some extension is relying on it. + */ + goToDay(date) { + this.showDate(date); + } + + getRangeDescription() { + return cal.dtz.formatter.formatInterval(this.rangeStartDate, this.rangeEndDate); + } + + removeDropShadows() { + this.querySelectorAll("[dropbox='true']").forEach(dbox => { + dbox.setAttribute("dropbox", "false"); + }); + } + + setDateRange(startDate, endDate) { + calendarNavigationBar.setDateRange(startDate, endDate); + } + + getSelectedItems() { + return this.mSelectedItems; + } + + setSelectedItems(items) { + this.mSelectedItems = items.concat([]); + return this.mSelectedItems; + } + + getDateList() { + const start = this.startDate.clone(); + const dateList = []; + while (start.compare(this.endDate) <= 0) { + dateList.push(start); + start.day++; + } + return dateList; + } + + zoomIn(level) {} + + zoomOut(level) {} + + zoomReset() {} + + // End calICalendarView Methods + } + + XPCOMUtils.defineLazyPreferenceGetter( + CalendarBaseView.prototype, + "weekStartOffset", + "calendar.week.start", + 0 + ); + + MozXULElement.implementCustomInterface(CalendarBaseView, [Ci.calICalendarView]); + + MozElements.CalendarBaseView = CalendarBaseView; +} diff --git a/comm/calendar/base/content/calendar-chrome-startup.js b/comm/calendar/base/content/calendar-chrome-startup.js new file mode 100644 index 0000000000..8a902923f9 --- /dev/null +++ b/comm/calendar/base/content/calendar-chrome-startup.js @@ -0,0 +1,438 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* exported calendarOnToolbarsPopupShowing, customizeMailToolbarForTabType, + * initViewCalendarPaneMenu, loadCalendarComponent, + */ + +/* globals loadCalendarManager, injectCalendarCommandController, getViewBox, + observeViewDaySelect, getViewBox, calendarController, calendarUpdateNewItemsCommand, + TodayPane, setUpInvitationsManager, changeMode, + prepareCalendarUnifinder, taskViewOnLoad, taskEdit, tearDownInvitationsManager, + unloadCalendarManager, removeCalendarCommandController, finishCalendarUnifinder, + PanelUI, changeMenuForTask, setupDeleteMenuitem, getMinimonth, currentView, + refreshEventTree, gCurrentMode, InitMessageMenu, onViewToolbarsPopupShowing, + onCommandCustomize, CustomizeMailToolbar */ + +var { AddonManager } = ChromeUtils.importESModule("resource://gre/modules/AddonManager.sys.mjs"); +var { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { calendarDeactivator } = ChromeUtils.import( + "resource:///modules/calendar/calCalendarDeactivator.jsm" +); + +ChromeUtils.defineModuleGetter(this, "CalMetronome", "resource:///modules/CalMetronome.jsm"); + +/** + * Does calendar initialization steps for a given chrome window. Called at + * startup as the application window is loaded, before tabs are restored. + */ +async function loadCalendarComponent() { + if (loadCalendarComponent.hasBeenCalled) { + cal.ERROR("loadCalendarComponent was called more than once for a single window"); + return; + } + loadCalendarComponent.hasBeenCalled = true; + + if (cal.manager.wrappedJSObject.mCache) { + cal.ASSERT( + [...Services.wm.getEnumerator("mail:3pane")].length > 1, + "Calendar manager initialised calendars before loadCalendarComponent ran on the first " + + "3pane window. This should not happen." + ); + } + + await uninstallLightningAddon(); + + // load locale specific default values for preferences + setLocaleDefaultPreferences(); + + // Move around toolbarbuttons and whatever is needed in the UI. + migrateCalendarUI(); + + // Load the Calendar Manager + await loadCalendarManager(); + + CalMetronome.on("day", doMidnightUpdate); + CalMetronome.on("minute", updateTimeIndicatorPosition); + + // Set up the command controller from calendar-command-controller.js + injectCalendarCommandController(); + + // Set up calendar deactivation for this window. + calendarDeactivator.registerWindow(window); + + // Set up item and day selection listeners + getViewBox().addEventListener("dayselect", observeViewDaySelect); + getViewBox().addEventListener("itemselect", calendarController.onSelectionChanged, true); + + // Start alarm service + Cc["@mozilla.org/calendar/alarm-service;1"].getService(Ci.calIAlarmService).startup(); + document.getElementById("calsidebar_splitter").addEventListener("command", () => { + window.dispatchEvent(new CustomEvent("viewresize")); + }); + document.getElementById("calendar-view-splitter").addEventListener("command", () => { + window.dispatchEvent(new CustomEvent("viewresize")); + }); + window.addEventListener("resize", event => { + if (event.target == window) { + window.dispatchEvent(new CustomEvent("viewresize")); + } + }); + + // Set calendar color CSS on this window + cal.view.colorTracker.registerWindow(window); + + /* Ensure the new items commands state can be setup properly even when no + * calendar support refreshes (i.e. the "onLoad" notification) or when none + * are active. In specific cases such as for file-based ICS calendars can + * happen, the initial "onLoad" will already have been triggered at this + * point (see bug 714431 comment 29). We thus unconditionally invoke + * calendarUpdateNewItemsCommand until somebody writes code that enables the + * checking of the calendar readiness (getProperty("ready") ?). + */ + calendarUpdateNewItemsCommand(); + + // Prepare the Today Pane, and if it is ready, display it. + await TodayPane.onLoad(); + + // Add an unload function to the window so we don't leak any listeners. + window.addEventListener("unload", unloadCalendarComponent); + + setUpInvitationsManager(); + + let filter = document.getElementById("task-tree-filtergroup"); + filter.value = filter.value || "all"; + + // Set up mode-switching menu items and mode[v]box elements for the initial mode. + // At this point no tabs have been restored, so the only reason we wouldn't be + // in "mail" mode is if a content tab has opened to display the account set-up. + let tabmail = document.getElementById("tabmail"); + if (tabmail.currentTabInfo.mode.name == "contentTab") { + changeMode("special"); + } else { + changeMode("mail"); + } + + updateTodayPaneButton(); + + prepareCalendarUnifinder(); + + taskViewOnLoad(); + taskEdit.onLoad(); + + document.getElementById("calSidebar").style.width = `${document + .getElementById("calSidebar") + .getAttribute("width")}px`; + + Services.obs.notifyObservers(window, "calendar-startup-done"); +} + +/** + * Does unload steps for a given calendar chrome window. + */ +function unloadCalendarComponent() { + tearDownInvitationsManager(); + + // Unload the calendar manager + unloadCalendarManager(); + + // Remove the command controller + removeCalendarCommandController(); + + finishCalendarUnifinder(); + + taskEdit.onUnload(); + + CalMetronome.off("minute", updateTimeIndicatorPosition); + CalMetronome.off("day", doMidnightUpdate); +} + +/** + * Uninstall the Lightning calendar addon, now that calendar is in Thunderbird. + */ +async function uninstallLightningAddon() { + try { + let addon = await AddonManager.getAddonByID("{e2fda1a4-762b-4020-b5ad-a41df1933103}"); + if (addon) { + await addon.uninstall(); + } + } catch (err) { + console.error("Error while attempting to uninstall Lightning addon:", err); + } +} +/** + * Migrate calendar UI. This function is called at each startup and can be used + * to change UI items that require js code intervention + */ +function migrateCalendarUI() { + const UI_VERSION = 3; + let currentUIVersion = Services.prefs.getIntPref("calendar.ui.version", 0); + if (currentUIVersion >= UI_VERSION) { + return; + } + + try { + if (currentUIVersion < 2) { + // If the user has customized the event/task window dialog toolbar, + // we copy that custom set of toolbar items to the event/task tab + // toolbar and add the app menu button and a spring for alignment. + let xulStore = Services.xulStore; + let uri = "chrome://calendar/content/calendar-event-dialog.xhtml"; + + if (xulStore.hasValue(uri, "event-toolbar", "currentset")) { + let windowSet = xulStore.getValue(uri, "event-toolbar", "currentset"); + let items = ""; + if (!windowSet.includes("spring")) { + items = "spring"; + } + let previousSet = windowSet == "__empty" ? "" : windowSet + ","; + let tabSet = previousSet + items; + let tabBar = document.getElementById("event-tab-toolbar"); + + tabBar.currentSet = tabSet; + // For some reason we also have to do the following, + // presumably because the toolbar has already been + // loaded into the DOM so the toolbar's currentset + // attribute does not yet match the new currentSet. + tabBar.setAttribute("currentset", tabSet); + } + } + if (currentUIVersion < 3) { + // Rename toolbar button id "button-save" to + // "button-saveandclose" in customized toolbars + let xulStore = Services.xulStore; + let windowUri = "chrome://calendar/content/calendar-event-dialog.xhtml"; + let tabUri = "chrome://messenger/content/messenger.xhtml"; + + if (xulStore.hasValue(windowUri, "event-toolbar", "currentset")) { + let windowSet = xulStore.getValue(windowUri, "event-toolbar", "currentset"); + let newSet = windowSet.replace("button-save", "button-saveandclose"); + xulStore.setValue(windowUri, "event-toolbar", "currentset", newSet); + } + if (xulStore.hasValue(tabUri, "event-tab-toolbar", "currentset")) { + let tabSet = xulStore.getValue(tabUri, "event-tab-toolbar", "currentset"); + let newSet = tabSet.replace("button-save", "button-saveandclose"); + xulStore.setValue(tabUri, "event-tab-toolbar", "currentset", newSet); + + let tabBar = document.getElementById("event-tab-toolbar"); + tabBar.currentSet = newSet; + tabBar.setAttribute("currentset", newSet); + } + } + Services.prefs.setIntPref("calendar.ui.version", UI_VERSION); + } catch (e) { + cal.ERROR("Error upgrading UI from " + currentUIVersion + " to " + UI_VERSION + ": " + e); + } +} + +function setLocaleDefaultPreferences() { + function setDefaultLocaleValue(aName) { + // Shift encoded days from 1=Monday ... 7=Sunday to 0=Sunday ... 6=Saturday + let startDefault = calendarInfo.firstDayOfWeek % 7; + + if (aName == "calendar.categories.names" && defaultBranch.getStringPref(aName) == "") { + cal.category.setupDefaultCategories(); + } else if (aName == "calendar.week.start" && defaultBranch.getIntPref(aName) != startDefault) { + defaultBranch.setIntPref(aName, startDefault); + } else if (aName.startsWith("calendar.week.d")) { + let dayNumber = parseInt(aName[15], 10); + if (dayNumber == 0) { + dayNumber = 7; + } + defaultBranch.setBoolPref(aName, calendarInfo.weekend.includes(dayNumber)); + } + } + + cal.LOG("Start loading of locale dependent preference default values..."); + + let defaultBranch = Services.prefs.getDefaultBranch(""); + let calendarInfo = cal.l10n.calendarInfo(); + + let prefDefaults = [ + "calendar.week.start", + "calendar.week.d0sundaysoff", + "calendar.week.d1mondaysoff", + "calendar.week.d2tuesdaysoff", + "calendar.week.d3wednesdaysoff", + "calendar.week.d4thursdaysoff", + "calendar.week.d5fridaysoff", + "calendar.week.d6saturdaysoff", + "calendar.categories.names", + ]; + for (let prefDefault of prefDefaults) { + setDefaultLocaleValue(prefDefault); + } + + cal.LOG("Loading of locale sensitive preference default values completed."); +} + +/** + * Called at midnight to tell us to redraw date-specific widgets. + */ +function doMidnightUpdate() { + try { + getMinimonth().refreshDisplay(); + + // Refresh the current view and just allow the refresh for the others + // views when will be displayed. + let currView = currentView(); + currView.goToDay(); + let views = ["day-view", "week-view", "multiweek-view", "month-view"]; + for (let view of views) { + if (view != currView.id) { + document.getElementById(view).mToggleStatus = -1; + } + } + + if (!TodayPane.showsToday()) { + TodayPane.setDay(cal.dtz.now()); + } + + // Update the unifinder. + refreshEventTree(); + + // Update today's date on todaypane button. + updateTodayPaneButtonDate(); + } catch (exc) { + cal.ASSERT(false, exc); + } +} + +/** + * Update the position of the current view's indicator of the current time, if + * any. + */ +function updateTimeIndicatorPosition() { + const view = currentView(); + if (!view?.isInitialized) { + // Ensure that we don't attempt to update a view that isn't ready. Calendar + // chrome is always loaded at startup, but the view isn't initialized until + // the user switches to the calendar tab. + return; + } + + view.updateTimeIndicatorPosition(); +} + +/** + * Updates button structure to enable images on both sides of the label. + */ +function updateTodayPaneButton() { + let todaypane = document.getElementById("calendar-status-todaypane-button"); + + let iconStack = document.createXULElement("stack"); + iconStack.setAttribute("pack", "center"); + iconStack.setAttribute("align", "end"); + + let iconBegin = document.createElement("img"); + iconBegin.setAttribute("alt", ""); + iconBegin.setAttribute("src", "chrome://messenger/skin/icons/new/calendar-empty.svg"); + iconBegin.classList.add("toolbarbutton-icon-begin"); + + let iconLabel = document.createXULElement("label"); + iconLabel.classList.add("toolbarbutton-day-text"); + + let dayNumber = cal.l10n.getDateFmtString(`day.${cal.dtz.now().day}.number`); + iconLabel.textContent = dayNumber; + + iconStack.appendChild(iconBegin); + iconStack.appendChild(iconLabel); + + let iconEnd = document.createElement("img"); + iconEnd.setAttribute("alt", ""); + iconEnd.setAttribute("src", "chrome://messenger/skin/icons/new/nav-up-sm.svg"); + iconEnd.classList.add("toolbarbutton-icon-end"); + + let oldImage = todaypane.querySelector(".toolbarbutton-icon"); + todaypane.replaceChild(iconStack, oldImage); + todaypane.appendChild(iconEnd); + + let calSidebar = document.getElementById("calSidebar"); + todaypane.setAttribute("checked", !calSidebar.collapsed); +} + +/** + * Updates the date number in the calendar icon of the todaypane button. + */ +function updateTodayPaneButtonDate() { + let todaypane = document.getElementById("calendar-status-todaypane-button"); + + let dayNumber = cal.l10n.getDateFmtString(`day.${cal.dtz.now().day}.number`); + todaypane.querySelector(".toolbarbutton-day-text").textContent = dayNumber; +} + +/** + * Get the toolbox id for the current tab type. + * + * @returns {string} A toolbox id. + */ +function getToolboxIdForCurrentTabType() { + // A mapping from calendar tab types to toolbox ids. + const calendarToolboxIds = { + calendar: null, + tasks: null, + calendarEvent: "event-toolbox", + calendarTask: "event-toolbox", + }; + let tabmail = document.getElementById("tabmail"); + if (!tabmail) { + return "mail-toolbox"; // Standalone message window. + } + let tabType = tabmail.currentTabInfo.mode.type; + + return calendarToolboxIds[tabType] || null; +} + +/** + * Modify the contents of the "Toolbars" context menu for the current + * tab type. Menu items are inserted before (appear above) aInsertPoint. + * + * @param {MouseEvent} aEvent - The popupshowing event + * @param {nsIDOMXULElement} aInsertPoint - (optional) menuitem node + */ +function calendarOnToolbarsPopupShowing(aEvent, aInsertPoint) { + if (onViewToolbarsPopupShowing.length < 3) { + // SeaMonkey + onViewToolbarsPopupShowing(aEvent); + return; + } + + let toolboxes = ["navigation-toolbox"]; + let toolboxId = getToolboxIdForCurrentTabType(); + + if (toolboxId) { + toolboxes.push(toolboxId); + } + + onViewToolbarsPopupShowing(aEvent, toolboxes, aInsertPoint); +} + +/** + * Open the customize dialog for the toolbar for the current tab type. + */ +function customizeMailToolbarForTabType() { + let toolboxId = getToolboxIdForCurrentTabType(); + if (!toolboxId) { + return; + } + if (toolboxId == "event-toolbox") { + onCommandCustomize(); + } else { + CustomizeMailToolbar(toolboxId, "CustomizeMailToolbar"); + } +} + +/** + * Initialize the calendar sidebar menu state. + */ +function initViewCalendarPaneMenu() { + let calSidebar = document.getElementById("calSidebar"); + + document.getElementById("calViewCalendarPane").setAttribute("checked", !calSidebar.collapsed); + + if (document.getElementById("appmenu_calViewCalendarPane")) { + document.getElementById("appmenu_calViewCalendarPane").checked = !calSidebar.collapsed; + } +} diff --git a/comm/calendar/base/content/calendar-clipboard.js b/comm/calendar/base/content/calendar-clipboard.js new file mode 100644 index 0000000000..d3a755d167 --- /dev/null +++ b/comm/calendar/base/content/calendar-clipboard.js @@ -0,0 +1,306 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals getSelectedCalendar, getSelectedItems, promptOccurrenceModification, + calendarViewController, currentView, startBatchTransaction, doTransaction, + endBatchTransaction */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +/* exported cutToClipboard, pasteFromClipboard */ + +/** + * Test if a writable calendar is selected, and if the clipboard has items that + * can be pasted into Calendar. The data must be of type "text/calendar" or + * "text/plain". + * + * @returns If true, pasting is currently possible. + */ +function canPaste() { + if (Services.prefs.getBoolPref("calendar.paste.intoSelectedCalendar", false)) { + let selectedCal = getSelectedCalendar(); + if ( + !selectedCal || + !cal.acl.isCalendarWritable(selectedCal) || + !cal.acl.userCanAddItemsToCalendar(selectedCal) + ) { + return false; + } + } else { + let calendars = cal.manager + .getCalendars() + .filter(cal.acl.isCalendarWritable) + .filter(cal.acl.userCanAddItemsToCalendar); + if (!calendars.length) { + return false; + } + } + + const flavors = ["text/calendar", "text/plain"]; + return Services.clipboard.hasDataMatchingFlavors(flavors, Ci.nsIClipboard.kGlobalClipboard); +} + +/** + * Copy the ics data of the current view's selected events to the clipboard and + * deletes the events on success + * + * @param aCalendarItemArray (optional) an array of items to cut. If not + * passed, the current view's selected items will + * be used. + */ +function cutToClipboard(aCalendarItemArray = null) { + copyToClipboard(aCalendarItemArray, true); +} + +/** + * Copy the ics data of the items in calendarItemArray to the clipboard. Fills + * both text/unicode and text/calendar mime types. + * + * @param aCalendarItemArray (optional) an array of items to copy. If not + * passed, the current view's selected items will + * be used. + * @param aCutMode (optional) set to true, if this is a cut operation + */ +function copyToClipboard(aCalendarItemArray = null, aCutMode = false) { + let calendarItemArray = aCalendarItemArray || getSelectedItems(); + if (!calendarItemArray.length) { + cal.LOG("[calendar-clipboard] No items selected."); + return; + } + if (aCutMode) { + let items = calendarItemArray.filter( + aItem => + cal.acl.userCanModifyItem(aItem) || + (aItem.calendar && cal.acl.userCanDeleteItemsFromCalendar(aItem.calendar)) + ); + if (items.length < calendarItemArray.length) { + cal.LOG("[calendar-clipboard] No privilege to delete some or all selected items."); + return; + } + calendarItemArray = items; + } + let [targetItems, , response] = promptOccurrenceModification( + calendarItemArray, + true, + aCutMode ? "cut" : "copy" + ); + if (!response) { + // The user canceled the dialog, bail out + return; + } + + let icsSerializer = Cc["@mozilla.org/calendar/ics-serializer;1"].createInstance( + Ci.calIIcsSerializer + ); + icsSerializer.addItems(targetItems); + let icsString = icsSerializer.serializeToString(); + + let clipboard = Services.clipboard; + let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + + if (trans && clipboard) { + // Register supported data flavors + trans.init(null); + trans.addDataFlavor("text/calendar"); + trans.addDataFlavor("text/plain"); + + // Create the data objects + let icsWrapper = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + icsWrapper.data = icsString; + + // Add data objects to transferable + // Both Outlook 2000 client and Lotus Organizer use text/unicode + // when pasting iCalendar data. + trans.setTransferData("text/calendar", icsWrapper); + trans.setTransferData("text/plain", icsWrapper); + + clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); + if (aCutMode) { + // check for MODIFICATION_PARENT + let useParent = response == 3; + calendarViewController.deleteOccurrences(targetItems, useParent, true); + } + } +} + +/** + * Reads ics data from the clipboard, parses it into items and inserts the items + * into the currently selected calendar. + */ +function pasteFromClipboard() { + if (!canPaste()) { + return; + } + + let clipboard = Services.clipboard; + let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + + if (!trans || !clipboard) { + return; + } + + // Register the wanted data flavors (highest fidelity first!) + trans.init(null); + trans.addDataFlavor("text/calendar"); + trans.addDataFlavor("text/plain"); + + // Get transferable from clipboard + clipboard.getData(trans, Ci.nsIClipboard.kGlobalClipboard); + + // Ask transferable for the best flavor. + let flavor = {}; + let data = {}; + trans.getAnyTransferData(flavor, data); + data = data.value.QueryInterface(Ci.nsISupportsString).data; + switch (flavor.value) { + case "text/calendar": + case "text/plain": { + let icsParser = Cc["@mozilla.org/calendar/ics-parser;1"].createInstance(Ci.calIIcsParser); + try { + icsParser.parseString(data); + } catch (e) { + // Ignore parser errors from the clipboard data, if it fails + // there will just be 0 items. + } + + let items = icsParser.getItems(); + if (items.length == 0) { + return; + } + + // If there are multiple items on the clipboard, the earliest + // should be set to the selected day and the rest adjusted. + let earliestDate = null; + for (let item of items) { + let date = null; + if (item.startDate) { + date = item.startDate.clone(); + } else if (item.entryDate) { + date = item.entryDate.clone(); + } else if (item.dueDate) { + date = item.dueDate.clone(); + } + + if (!date) { + continue; + } + + if (!earliestDate || date.compare(earliestDate) < 0) { + earliestDate = date; + } + } + let firstDate = currentView().selectedDay; + + let offset = null; + if (earliestDate) { + // Timezones and DT/DST time may differ between the earliest item + // and the selected day. Determine the offset between the + // earliestDate in local time and the selected day in whole days. + earliestDate = earliestDate.getInTimezone(cal.dtz.defaultTimezone); + earliestDate.isDate = true; + offset = firstDate.subtractDate(earliestDate); + let deltaDST = firstDate.timezoneOffset - earliestDate.timezoneOffset; + offset.inSeconds += deltaDST; + } + + // we only will need to ask whether to send notifications, if there + // are attendees at all + let withAttendees = items.filter(aItem => aItem.getAttendees().length > 0); + + let notify = Ci.calIItipItem.USER; + let destCal = null; + if (Services.prefs.getBoolPref("calendar.paste.intoSelectedCalendar", false)) { + destCal = getSelectedCalendar(); + } else { + let pasteText = "paste"; + if (withAttendees.length) { + if (withAttendees.every(item => item.isEvent())) { + pasteText += "Event"; + } else if (withAttendees.every(item => item.isTodo())) { + pasteText += "Task"; + } else { + pasteText += "Item"; + } + if (withAttendees.length > 1) { + pasteText += "s"; + } + } + let validPasteText = pasteText != "paste" && !pasteText.endsWith("Item"); + pasteText += items.length == withAttendees.length ? "Only" : "Also"; + + let calendars = cal.manager + .getCalendars() + .filter(cal.acl.isCalendarWritable) + .filter(cal.acl.userCanAddItemsToCalendar) + .filter(aCal => { + let status = aCal.getProperty("currentStatus"); + return Components.isSuccessCode(status); + }); + if (calendars.length > 1) { + let args = {}; + args.calendars = calendars; + args.promptText = cal.l10n.getCalString("pastePrompt"); + + if (validPasteText) { + pasteText = cal.l10n.getCalString(pasteText); + let note = cal.l10n.getCalString("pasteNotifyAbout", [pasteText]); + args.promptNotify = note; + + args.labelExtra1 = cal.l10n.getCalString("pasteDontNotifyLabel"); + args.onExtra1 = aCal => { + destCal = aCal; + notify = Ci.calIItipItem.NONE; + }; + args.labelOk = cal.l10n.getCalString("pasteAndNotifyLabel"); + args.onOk = aCal => { + destCal = aCal; + notify = Ci.calIItipItem.AUTO; + }; + } else { + args.onOk = aCal => { + destCal = aCal; + }; + } + + window.openDialog( + "chrome://calendar/content/chooseCalendarDialog.xhtml", + "_blank", + "chrome,titlebar,modal,resizable", + args + ); + } else if (calendars.length == 1) { + destCal = calendars[0]; + } + } + if (!destCal) { + return; + } + + startBatchTransaction(); + for (let item of items) { + // TODO: replace the UUID only it it already exists in the + // calendar to avoid to break invitation scenarios where remote + // parties rely on the UUID. + let newItem = item.clone(); + // Set new UID to allow multiple paste actions of the same + // clipboard content. + newItem.id = cal.getUUID(); + if (offset) { + cal.item.shiftOffset(newItem, offset); + } + + let extResp = { responseMode: Ci.calIItipItem.NONE }; + if (item.getAttendees().length > 0) { + extResp.responseMode = notify; + } + + doTransaction("add", newItem, destCal, null, null, extResp); + } + endBatchTransaction(); + break; + } + default: + break; + } +} diff --git a/comm/calendar/base/content/calendar-command-controller.js b/comm/calendar/base/content/calendar-command-controller.js new file mode 100644 index 0000000000..605b2e9a58 --- /dev/null +++ b/comm/calendar/base/content/calendar-command-controller.js @@ -0,0 +1,869 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals goUpdateCommand, currentView, TodayPane, createEventWithDialog, + getSelectedCalendar, editSelectedEvents, viewSelectedEvents, + modifyTaskFromContext, deleteSelectedEvents, setupAttendanceMenu, + createTodoWithDialog, deleteToDoCommand, promptDeleteCalendar, + toImport, loadEventsFromFile, exportEntireCalendar, saveEventsToFile, + publishEntireCalendar, publishCalendarData, toggleUnifinder, toggleOrientation + toggleWorkdaysOnly, switchCalendarView, getTaskTree, selectAllEvents, + gCurrentMode, getSelectedTasks, canPaste, goSetMenuValue, canUndo, canRedo, + cutToClipboard, copyToClipboard, pasteFromClipboard, undo, redo, + PrintUtils */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +var CalendarDeleteCommandEnabled = false; +var CalendarNewEventsCommandEnabled = false; +var CalendarNewTasksCommandEnabled = false; + +/** + * Command controller to execute calendar specific commands + * + * @see nsICommandController + */ +var calendarController = { + commands: new Set([ + // Common commands + "calendar_new_event_command", + "calendar_new_event_context_command", + "calendar_new_event_todaypane_command", + "calendar_modify_event_command", + "calendar_view_event_command", + "calendar_delete_event_command", + + "calendar_modify_focused_item_command", + "calendar_delete_focused_item_command", + + "calendar_new_todo_command", + "calendar_new_todo_context_command", + "calendar_new_todo_todaypane_command", + "calendar_toggle_tasks_in_view_command", + "calendar_modify_todo_command", + "calendar_modify_todo_todaypane_command", + "calendar_delete_todo_command", + + "calendar_new_calendar_command", + "calendar_edit_calendar_command", + "calendar_delete_calendar_command", + + "calendar_import_command", + "calendar_export_command", + "calendar_export_selection_command", + + "calendar_publish_selected_calendar_command", + "calendar_publish_calendar_command", + "calendar_publish_selected_events_command", + + "calendar_view_next_command", + "calendar_view_prev_command", + + "calendar_toggle_orientation_command", + "calendar_toggle_workdays_only_command", + + "calendar_day-view_command", + "calendar_week-view_command", + "calendar_multiweek-view_command", + "calendar_month-view_command", + + "calendar_task_filter_command", + "calendar_reload_remote_calendars", + "calendar_show_unifinder_command", + "calendar_toggle_completed_command", + "calendar_percentComplete-0_command", + "calendar_percentComplete-25_command", + "calendar_percentComplete-50_command", + "calendar_percentComplete-75_command", + "calendar_percentComplete-100_command", + "calendar_priority-0_command", + "calendar_priority-9_command", + "calendar_priority-5_command", + "calendar_priority-1_command", + "calendar_general-priority_command", + "calendar_general-progress_command", + "calendar_general-postpone_command", + "calendar_postpone-1hour_command", + "calendar_postpone-1day_command", + "calendar_postpone-1week_command", + "calendar_task_category_command", + + "calendar_attendance_command", + + // for events/tasks in a tab + "cmd_save", + "cmd_accept", + + // Pseudo commands + "calendar_in_foreground", + "calendar_in_background", + "calendar_mode_calendar", + "calendar_mode_task", + + "cmd_selectAll", + ]), + + updateCommands() { + this.commands.forEach(goUpdateCommand); + }, + + supportsCommand(aCommand) { + if (this.commands.has(aCommand)) { + return true; + } + return false; + }, + + /* eslint-disable complexity */ + isCommandEnabled(aCommand) { + switch (aCommand) { + case "calendar_new_event_command": + case "calendar_new_event_context_command": + case "calendar_new_event_todaypane_command": + return CalendarNewEventsCommandEnabled; + case "calendar_modify_focused_item_command": + return this.item_selected && canEditSelectedItems(); + case "calendar_modify_event_command": + return this.item_selected && canEditSelectedItems(); + case "calendar_view_event_command": + return this.item_selected; + case "calendar_delete_focused_item_command": + return CalendarDeleteCommandEnabled && this.selected_items_writable; + case "calendar_delete_event_command": + return CalendarDeleteCommandEnabled && this.selected_items_writable; + case "calendar_new_todo_command": + case "calendar_new_todo_context_command": + case "calendar_new_todo_todaypane_command": + case "calendar_toggle_tasks_in_view_command": + return CalendarNewTasksCommandEnabled; + case "calendar_modify_todo_command": + case "calendar_modify_todo_todaypane_command": + return this.todo_items_selected; + // This code is temporarily commented out due to + // bug 469684 Unifinder-todo: raising of the context menu fires blur-event + // this.todo_tasktree_focused; + case "calendar_edit_calendar_command": + return this.isCalendarInForeground(); + case "calendar_task_filter_command": + return true; + case "calendar_delete_todo_command": + if (!CalendarDeleteCommandEnabled) { + return false; + } + // falls through otherwise + case "calendar_toggle_completed_command": + case "calendar_percentComplete-0_command": + case "calendar_percentComplete-25_command": + case "calendar_percentComplete-50_command": + case "calendar_percentComplete-75_command": + case "calendar_percentComplete-100_command": + case "calendar_priority-0_command": + case "calendar_priority-9_command": + case "calendar_priority-5_command": + case "calendar_priority-1_command": + case "calendar_task_category_command": + case "calendar_general-progress_command": + case "calendar_general-priority_command": + case "calendar_general-postpone_command": + case "calendar_postpone-1hour_command": + case "calendar_postpone-1day_command": + case "calendar_postpone-1week_command": + return ( + ((this.isCalendarInForeground() || this.todo_tasktree_focused) && + this.writable && + this.todo_items_selected && + this.todo_items_writable) || + document.getElementById("tabmail").currentTabInfo.mode.type == "calendarTask" + ); + case "calendar_delete_calendar_command": + return this.isCalendarInForeground() && !this.last_calendar; + case "calendar_import_command": + return this.writable; + case "calendar_export_selection_command": + return this.item_selected; + case "calendar_toggle_orientation_command": + return this.isInMode("calendar") && currentView().supportsRotation; + case "calendar_toggle_workdays_only_command": + return this.isInMode("calendar") && currentView().supportsWorkdaysOnly; + case "calendar_publish_selected_events_command": + return this.item_selected; + + case "calendar_reload_remote_calendars": + return this.has_enabled_reloadable_calendars && !this.offline; + case "calendar_attendance_command": { + let attendSel = false; + if (this.todo_tasktree_focused) { + attendSel = + this.writable && + this.todo_items_invitation && + this.todo_items_selected && + this.todo_items_writable; + } else { + attendSel = + this.item_selected && this.selected_events_invitation && this.selected_items_writable; + } + + // Small hack, we want to hide instead of disable. + document.getElementById("calendar_attendance_command").setAttribute("hidden", !attendSel); + return attendSel; + } + + // The following commands all just need the calendar in foreground, + // make sure you take care when changing things here. + case "calendar_view_next_command": + case "calendar_view_prev_command": + case "calendar_in_foreground": + return this.isCalendarInForeground(); + case "calendar_in_background": + return !this.isCalendarInForeground(); + + // The following commands need calendar mode, be careful when + // changing things. + case "calendar_day-view_command": + case "calendar_week-view_command": + case "calendar_multiweek-view_command": + case "calendar_month-view_command": + case "calendar_show_unifinder_command": + case "calendar_mode_calendar": + return this.isInMode("calendar"); + + case "calendar_mode_task": + return this.isInMode("task"); + + case "cmd_selectAll": + return this.todo_tasktree_focused || this.isInMode("calendar"); + + // for events/tasks in a tab + case "cmd_save": + // falls through + case "cmd_accept": { + let tabType = document.getElementById("tabmail").currentTabInfo.mode.type; + return tabType == "calendarTask" || tabType == "calendarEvent"; + } + + default: + if (this.commands.has(aCommand)) { + // All other commands we support should be enabled by default + return true; + } + } + return false; + }, + /* eslint-enable complexity */ + + doCommand(aCommand) { + switch (aCommand) { + // Common Commands + case "calendar_new_event_command": + createEventWithDialog( + getSelectedCalendar(), + cal.dtz.getDefaultStartDate(currentView().selectedDay) + ); + break; + case "calendar_new_event_context_command": { + let newStart = currentView().selectedDateTime; + if (!newStart) { + newStart = cal.dtz.getDefaultStartDate(currentView().selectedDay); + } + createEventWithDialog(getSelectedCalendar(), newStart, null, null, null, newStart.isDate); + break; + } + case "calendar_new_event_todaypane_command": + createEventWithDialog(getSelectedCalendar(), cal.dtz.getDefaultStartDate(TodayPane.start)); + break; + case "calendar_modify_event_command": + editSelectedEvents(); + break; + case "calendar_view_event_command": + viewSelectedEvents(); + break; + case "calendar_modify_focused_item_command": { + let focusedElement = document.commandDispatcher.focusedElement; + if (focusedElement == TodayPane.agenda) { + TodayPane.agenda.editSelectedItem(); + } else if (focusedElement && focusedElement.className == "calendar-task-tree") { + modifyTaskFromContext(); + } else if (this.isInMode("calendar")) { + editSelectedEvents(); + } + break; + } + case "calendar_delete_event_command": + deleteSelectedEvents(); + break; + case "calendar_delete_focused_item_command": { + let focusedElement = document.commandDispatcher.focusedElement; + if (focusedElement == TodayPane.agenda) { + TodayPane.agenda.deleteSelectedItem(false); + } else if (focusedElement && focusedElement.className == "calendar-task-tree") { + deleteToDoCommand(false); + } else if (this.isInMode("calendar")) { + deleteSelectedEvents(); + } + break; + } + case "calendar_new_todo_command": + createTodoWithDialog( + getSelectedCalendar(), + null, + null, + null, + cal.dtz.getDefaultStartDate(currentView().selectedDay) + ); + break; + case "calendar_new_todo_context_command": { + let initialDate = currentView().selectedDateTime; + if (!initialDate || initialDate.isDate) { + initialDate = cal.dtz.getDefaultStartDate(currentView().selectedDay); + } + createTodoWithDialog(getSelectedCalendar(), null, null, null, initialDate); + break; + } + case "calendar_new_todo_todaypane_command": + createTodoWithDialog( + getSelectedCalendar(), + null, + null, + null, + cal.dtz.getDefaultStartDate(TodayPane.start) + ); + break; + case "calendar_delete_todo_command": + deleteToDoCommand(); + break; + case "calendar_modify_todo_command": + modifyTaskFromContext(cal.dtz.getDefaultStartDate(currentView().selectedDay)); + break; + case "calendar_modify_todo_todaypane_command": + modifyTaskFromContext(cal.dtz.getDefaultStartDate(TodayPane.start)); + break; + + case "calendar_new_calendar_command": + cal.window.openCalendarWizard(window); + break; + case "calendar_edit_calendar_command": + cal.window.openCalendarProperties(window, { calendar: getSelectedCalendar() }); + break; + case "calendar_delete_calendar_command": + promptDeleteCalendar(getSelectedCalendar()); + break; + + case "calendar_import_command": + if (Services.prefs.getBoolPref("mail.import.in_new_tab")) { + toImport("calendar"); + } else { + loadEventsFromFile(); + } + break; + case "calendar_export_command": + exportEntireCalendar(); + break; + case "calendar_export_selection_command": + saveEventsToFile(currentView().getSelectedItems()); + break; + + case "calendar_publish_selected_calendar_command": + publishEntireCalendar(getSelectedCalendar()); + break; + case "calendar_publish_calendar_command": + publishEntireCalendar(); + break; + case "calendar_publish_selected_events_command": + publishCalendarData(); + break; + + case "calendar_reload_remote_calendars": + cal.view.getCompositeCalendar(window).refresh(); + break; + case "calendar_show_unifinder_command": + toggleUnifinder(); + break; + case "calendar_view_next_command": + currentView().moveView(1); + break; + case "calendar_view_prev_command": + currentView().moveView(-1); + break; + case "calendar_toggle_orientation_command": + toggleOrientation(); + break; + case "calendar_toggle_workdays_only_command": + toggleWorkdaysOnly(); + break; + + case "calendar_day-view_command": + switchCalendarView("day", true); + break; + case "calendar_week-view_command": + switchCalendarView("week", true); + break; + case "calendar_multiweek-view_command": + switchCalendarView("multiweek", true); + break; + case "calendar_month-view_command": + switchCalendarView("month", true); + break; + case "calendar_attendance_command": + // This command is actually handled inline, since it takes a value + break; + + case "cmd_selectAll": + if (this.todo_tasktree_focused) { + getTaskTree().selectAll(); + } else if (this.isInMode("calendar")) { + selectAllEvents(); + } + break; + } + }, + + onEvent(aEvent) {}, + + isCalendarInForeground() { + return gCurrentMode && gCurrentMode != "mail"; + }, + + isInMode(mode) { + switch (mode) { + case "mail": + return !this.isCalendarInForeground(); + case "calendar": + return gCurrentMode && gCurrentMode == "calendar"; + case "task": + return gCurrentMode && gCurrentMode == "task"; + } + return false; + }, + + onSelectionChanged(aEvent) { + let selectedItems = aEvent.detail; + + calendarUpdateDeleteCommand(selectedItems); + calendarController.item_selected = selectedItems && selectedItems.length > 0; + + let selLength = selectedItems === undefined ? 0 : selectedItems.length; + let selected_events_readonly = 0; + let selected_events_requires_network = 0; + let selected_events_invitation = 0; + + if (selLength > 0) { + for (let item of selectedItems) { + if (item.calendar.readOnly) { + selected_events_readonly++; + } + if ( + item.calendar.getProperty("requiresNetwork") && + !item.calendar.getProperty("cache.enabled") && + !item.calendar.getProperty("cache.always") + ) { + selected_events_requires_network++; + } + + if (cal.itip.isInvitation(item)) { + selected_events_invitation++; + } else if (item.organizer) { + // If we are the organizer and there are attendees, then + // this is likely also an invitation. + let calOrgId = item.calendar.getProperty("organizerId"); + if (item.organizer.id == calOrgId && item.getAttendees().length) { + selected_events_invitation++; + } + } + } + } + + calendarController.selected_events_readonly = selected_events_readonly == selLength; + + calendarController.selected_events_requires_network = + selected_events_requires_network == selLength; + calendarController.selected_events_invitation = selected_events_invitation == selLength; + + calendarController.updateCommands(); + calendarController2.updateCommands(); + document.commandDispatcher.updateCommands("mail-toolbar"); + }, + + /** + * Condition Helpers + */ + + // These attributes will be set up manually. + item_selected: false, + selected_events_readonly: false, + selected_events_requires_network: false, + selected_events_invitation: false, + + /** + * Returns a boolean indicating if its possible to write items to any + * calendar. + */ + get writable() { + return cal.manager.getCalendars().some(cal.acl.isCalendarWritable); + }, + + /** + * Returns a boolean indicating if the application is currently in offline + * mode. + */ + get offline() { + return Services.io.offline; + }, + + /** + * Returns a boolean indicating whether there is at least one enabled + * calendar that can be reloaded. Note: ICS calendars can have a network URL + * or a file URL, but both are reloadable. + */ + get has_enabled_reloadable_calendars() { + return cal.manager + .getCalendars() + .some( + calendar => + !calendar.getProperty("disabled") && + (calendar.type == "ics" || calendar.getProperty("requiresNetwork") !== false) + ); + }, + + /** + * Returns a boolean indicating that there is only one calendar left. + */ + get last_calendar() { + return cal.manager.calendarCount < 2; + }, + + /** + * Returns a boolean indicating that at least one of the items selected + * in the current view has a writable calendar. + */ + get selected_items_writable() { + return ( + this.writable && + this.item_selected && + !this.selected_events_readonly && + (!this.offline || !this.selected_events_requires_network) + ); + }, + + /** + * Returns a boolean indicating that tasks are selected. + */ + get todo_items_selected() { + let selectedTasks = getSelectedTasks(); + return selectedTasks.length > 0; + }, + + get todo_items_invitation() { + let selectedTasks = getSelectedTasks(); + let selected_tasks_invitation = 0; + + for (let item of selectedTasks) { + if (cal.itip.isInvitation(item)) { + selected_tasks_invitation++; + } else if (item.organizer) { + // If we are the organizer and there are attendees, then + // this is likely also an invitation. + let calOrgId = item.calendar.getProperty("organizerId"); + if (item.organizer.id == calOrgId && item.getAttendees().length) { + selected_tasks_invitation++; + } + } + } + + return selectedTasks.length == selected_tasks_invitation; + }, + + /** + * Returns a boolean indicating that at least one task in the selection is + * on a calendar that is writable. + */ + get todo_items_writable() { + let selectedTasks = getSelectedTasks(); + for (let task of selectedTasks) { + if (cal.acl.isCalendarWritable(task.calendar)) { + return true; + } + } + return false; + }, +}; + +/** + * XXX This is a temporary hack so we can release 1.0b2. This will soon be + * superseded by a new command controller architecture. + */ +var calendarController2 = { + commands: new Set([ + "cmd_cut", + "cmd_copy", + "cmd_paste", + "cmd_undo", + "cmd_redo", + "cmd_print", + "button_print", + "button_delete", + "cmd_delete", + "cmd_properties", + "cmd_goForward", + "cmd_goBack", + "cmd_fullZoomReduce", + "cmd_fullZoomEnlarge", + "cmd_fullZoomReset", + "cmd_showQuickFilterBar", + ]), + + // These functions can use the same from the calendar controller for now. + updateCommands: calendarController.updateCommands, + supportsCommand: calendarController.supportsCommand, + onEvent: calendarController.onEvent, + + isCommandEnabled(aCommand) { + switch (aCommand) { + // Thunderbird Commands + case "cmd_cut": + return calendarController.selected_items_writable; + case "cmd_copy": + return calendarController.item_selected; + case "cmd_paste": + return canPaste(); + case "cmd_undo": + goSetMenuValue(aCommand, "valueDefault"); + return canUndo(); + case "cmd_redo": + goSetMenuValue(aCommand, "valueDefault"); + return canRedo(); + case "button_delete": + case "cmd_delete": + return calendarController.isCommandEnabled("calendar_delete_focused_item_command"); + case "cmd_fullZoomReduce": + case "cmd_fullZoomEnlarge": + case "cmd_fullZoomReset": + return calendarController.isInMode("calendar") && currentView().supportsZoom; + case "cmd_properties": + return false; + case "cmd_showQuickFilterBar": + return calendarController.isInMode("task"); + default: + return true; + } + }, + + doCommand(aCommand) { + if (!this.isCommandEnabled(aCommand)) { + // doCommand is triggered for cmd_cut even if the command is disabled + // so we bail out here + return; + } + switch (aCommand) { + case "cmd_cut": + cutToClipboard(); + break; + case "cmd_copy": + copyToClipboard(); + break; + case "cmd_paste": + pasteFromClipboard(); + break; + case "cmd_undo": + undo(); + break; + case "cmd_redo": + redo(); + break; + case "button_print": + case "cmd_print": + printCalendar(); + break; + // Thunderbird commands + case "cmd_goForward": + currentView().moveView(1); + break; + case "cmd_goBack": + currentView().moveView(-1); + break; + case "cmd_fullZoomReduce": + currentView().zoomIn(); + break; + case "cmd_fullZoomEnlarge": + currentView().zoomOut(); + break; + case "cmd_fullZoomReset": + currentView().zoomReset(); + break; + case "cmd_showQuickFilterBar": + document.getElementById("task-text-filter-field").select(); + break; + + case "button_delete": + case "cmd_delete": + calendarController.doCommand("calendar_delete_focused_item_command"); + break; + } + }, +}; + +/** + * Inserts the command controller into the document. Make sure that it is + * inserted before the conflicting Thunderbird command controller. + */ +function injectCalendarCommandController() { + // This is the third-highest priority controller. It's preceded by + // DefaultController and tabmail.tabController, and followed by + // calendarController, then whatever Gecko adds. + top.controllers.insertControllerAt(2, calendarController); + document.commandDispatcher.updateCommands("calendar_commands"); +} + +/** + * Remove the calendar command controller from the document. + */ +function removeCalendarCommandController() { + top.controllers.removeController(calendarController); +} + +/** + * Handler function to set up the item context menu, depending on the given + * items. Changes the delete menuitem to fit the passed items. + * + * @param {DOMEvent} aEvent The DOM popupshowing event that is + * triggered by opening the context menu + * @param {Array.} aItems An array of items (usually the selected + * items) to adapt the context menu for + * @returns {boolean} True, to show the popup menu. + */ +function setupContextItemType(aEvent, aItems) { + function adaptModificationMenuItem(aMenuItemId, aItemType) { + let menuItem = document.getElementById(aMenuItemId); + if (menuItem) { + menuItem.setAttribute("label", cal.l10n.getCalString(`delete${aItemType}Label`)); + menuItem.setAttribute("accesskey", cal.l10n.getCalString(`delete${aItemType}Accesskey`)); + } + } + if (aItems.some(item => item.isEvent()) && aItems.some(item => item.isTodo())) { + aEvent.target.setAttribute("type", "mixed"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Item"); + } else if (aItems.length && aItems[0].isEvent()) { + aEvent.target.setAttribute("type", "event"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Event"); + } else if (aItems.length && aItems[0].isTodo()) { + aEvent.target.setAttribute("type", "todo"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Task"); + } else { + aEvent.target.removeAttribute("type"); + adaptModificationMenuItem("calendar-item-context-menu-delete-menuitem", "Item"); + } + + let menu = document.getElementById("calendar-item-context-menu-attendance-menu"); + setupAttendanceMenu(menu, aItems); + return true; +} + +/** + * Tests whether the items currently selected can be edited in the event dialog. + * Invitations are not considered editable here. + */ +function canEditSelectedItems() { + let items = currentView().getSelectedItems(); + return items.every(item => { + let calendar = item.calendar; + return ( + cal.acl.isCalendarWritable(calendar) && + cal.acl.userCanModifyItem(item) && + calendar.supportsScheduling && + !calendar.getSchedulingSupport().isInvitation(item) + ); + }); +} + +/** + * Returns the selected items, based on which mode we are currently in and what task tree is focused. + */ +function getSelectedItems() { + if (calendarController.todo_tasktree_focused) { + return getSelectedTasks(); + } + + return currentView().getSelectedItems(); +} + +/** + * Deletes the selected items, based on which mode we are currently in and what task tree is focused + */ +function deleteSelectedItems() { + if (calendarController.todo_tasktree_focused) { + deleteToDoCommand(); + } else if (calendarController.isInMode("calendar")) { + deleteSelectedEvents(); + } +} + +/** + * Checks if any calendar allows new events and tasks to be added, otherwise + * disables the creation buttons. + */ +function calendarUpdateNewItemsCommand() { + // Re-calculate command status. + let calendars = cal.manager + .getCalendars() + .filter(cal.acl.isCalendarWritable) + .filter(cal.acl.userCanAddItemsToCalendar); + + CalendarNewEventsCommandEnabled = calendars.some(cal.item.isEventCalendar); + CalendarNewTasksCommandEnabled = calendars.some(cal.item.isTaskCalendar); + + [ + "calendar_new_event_command", + "calendar_new_event_context_command", + "calendar_new_event_todaypane_command", + "calendar_new_todo_command", + "calendar_new_todo_context_command", + "calendar_new_todo_todaypane_command", + "calendar_toggle_tasks_in_view_command", + ].forEach(goUpdateCommand); + + document.getElementById("sidePanelNewEvent").disabled = !CalendarNewEventsCommandEnabled; + document.getElementById("sidePanelNewTask").disabled = !CalendarNewTasksCommandEnabled; +} + +function calendarUpdateDeleteCommand(selectedItems) { + let oldValue = CalendarDeleteCommandEnabled; + CalendarDeleteCommandEnabled = selectedItems.length > 0; + + /* we must disable "delete" when at least one item cannot be deleted */ + for (let item of selectedItems) { + if (!cal.acl.userCanDeleteItemsFromCalendar(item.calendar)) { + CalendarDeleteCommandEnabled = false; + break; + } + } + + if (CalendarDeleteCommandEnabled != oldValue) { + [ + "calendar_delete_event_command", + "calendar_delete_todo_command", + "calendar_delete_focused_item_command", + "button_delete", + "cmd_delete", + ].forEach(goUpdateCommand); + } +} + +/** + * Loads the printing template into a hidden browser then starts the printing + * process for that browser. + */ +async function printCalendar() { + // Ensure the printing of this file will be detected by calPrintUtils.jsm. + cal.print.ensureInitialized(); + + await PrintUtils.loadPrintBrowser("chrome://calendar/content/printing-template.html"); + PrintUtils.startPrintWindow(PrintUtils.printBrowser.browsingContext, {}); +} +/** + * Toggle the visibility of the calendars list. + * + * @param {Event} event - The click DOMEvent. + */ +function toggleVisibilityCalendarsList(event) { + document.getElementById("calendar-list-inner-pane").togglePane(event); +} diff --git a/comm/calendar/base/content/calendar-commands.inc.xhtml b/comm/calendar/base/content/calendar-commands.inc.xhtml new file mode 100644 index 0000000000..78bce756ca --- /dev/null +++ b/comm/calendar/base/content/calendar-commands.inc.xhtml @@ -0,0 +1,101 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/calendar/base/content/calendar-context-menus-and-tooltips.inc.xhtml b/comm/calendar/base/content/calendar-context-menus-and-tooltips.inc.xhtml new file mode 100644 index 0000000000..f408dcca84 --- /dev/null +++ b/comm/calendar/base/content/calendar-context-menus-and-tooltips.inc.xhtml @@ -0,0 +1,949 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/calendar/base/content/calendar-day-label.js b/comm/calendar/base/content/calendar-day-label.js new file mode 100644 index 0000000000..e0d205bc5d --- /dev/null +++ b/comm/calendar/base/content/calendar-day-label.js @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global MozXULElement, getSummarizedStyleValues */ + +// Wrap in a block to prevent leaking to window scope. +{ + const { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + + class MozCalendarDayLabel extends MozXULElement { + static get observedAttributes() { + return ["selected", "relation"]; + } + + connectedCallback() { + if (this.delayConnectedCallback()) { + return; + } + this.textContent = ""; + this.setAttribute("flex", "1"); + this.setAttribute("pack", "center"); + + this.longWeekdayName = document.createXULElement("label"); + this.longWeekdayName.classList.add("calendar-day-label-name"); + + this.shortWeekdayName = document.createXULElement("label"); + this.shortWeekdayName.classList.add("calendar-day-label-name"); + this.shortWeekdayName.setAttribute("hidden", "true"); + + this.appendChild(this.longWeekdayName); + this.appendChild(this.shortWeekdayName); + + this.mWeekday = -1; + + this.longWeekdayPixels = 0; + + this.mDate = null; + + this._updateAttributes(); + } + + attributeChangedCallback() { + this._updateAttributes(); + } + + _updateAttributes() { + if (!this.longWeekdayName || !this.shortWeekdayName) { + return; + } + + if (this.hasAttribute("selected")) { + this.longWeekdayName.setAttribute("selected", this.getAttribute("selected")); + this.shortWeekdayName.setAttribute("selected", this.getAttribute("selected")); + } else { + this.longWeekdayName.removeAttribute("selected"); + this.shortWeekdayName.removeAttribute("selected"); + } + + if (this.hasAttribute("relation")) { + this.longWeekdayName.setAttribute("relation", this.getAttribute("relation")); + this.shortWeekdayName.setAttribute("relation", this.getAttribute("relation")); + } else { + this.longWeekdayName.removeAttribute("relation"); + this.shortWeekdayName.removeAttribute("relation"); + } + } + + set weekDay(val) { + this.mWeekday = val % 7; + this.longWeekdayName.value = cal.dtz.formatter.dayName(val); + this.shortWeekdayName.value = cal.dtz.formatter.shortDayName(val); + } + + get weekDay() { + return this.mWeekday; + } + + set date(val) { + this.mDate = val; + let dateFormatter = cal.dtz.formatter; + let label = cal.l10n.getCalString("dayHeaderLabel", [ + dateFormatter.shortDayName(val.weekday), + dateFormatter.formatDateWithoutYear(val), + ]); + this.shortWeekdayName.setAttribute("value", label); + label = cal.l10n.getCalString("dayHeaderLabel", [ + dateFormatter.dayName(val.weekday), + dateFormatter.formatDateWithoutYear(val), + ]); + this.longWeekdayName.setAttribute("value", label); + } + + get date() { + return this.mDate; + } + + set shortWeekNames(val) { + // cache before change, in case we are switching to short + this.getLongWeekdayPixels(); + this.longWeekdayName.hidden = val; + this.shortWeekdayName.hidden = !val; + } + + getLongWeekdayPixels() { + // Only do this if the long weekdays are visible and we haven't already cached. + let longNameWidth = this.longWeekdayName.getBoundingClientRect().width; + + if (longNameWidth == 0) { + // weekdaypixels have not yet been laid out + return 0; + } + + this.longWeekdayPixels = + longNameWidth + + getSummarizedStyleValues(this.longWeekdayName, ["margin-left", "margin-right"]); + this.longWeekdayPixels += getSummarizedStyleValues(this, [ + "border-left-width", + "padding-left", + "padding-right", + ]); + + return this.longWeekdayPixels; + } + } + + customElements.define("calendar-day-label", MozCalendarDayLabel); +} diff --git a/comm/calendar/base/content/calendar-dnd-listener.js b/comm/calendar/base/content/calendar-dnd-listener.js new file mode 100644 index 0000000000..994d305c95 --- /dev/null +++ b/comm/calendar/base/content/calendar-dnd-listener.js @@ -0,0 +1,922 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals getSelectedCalendar, MODE_RDONLY, startBatchTransaction, doTransaction, + endBatchTransaction, createEventWithDialog, createTodoWithDialog */ + +/* exported invokeEventDragSession, + * calendarMailButtonDNDObserver, calendarCalendarButtonDNDObserver, + * calendarTaskButtonDNDObserver + */ + +var calendarViewDNDObserver; +var calendarMailButtonDNDObserver; +var calendarCalendarButtonDNDObserver; +var calendarTaskButtonDNDObserver; + +// Wrap in a block to prevent leaking to window scope. +{ + var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + var { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); + var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + var { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + + XPCOMUtils.defineLazyModuleGetters(this, { + CalAttachment: "resource:///modules/CalAttachment.jsm", + CalAttendee: "resource:///modules/CalAttendee.jsm", + CalEvent: "resource:///modules/CalEvent.jsm", + CalTodo: "resource:///modules/CalTodo.jsm", + }); + + var itemConversion = { + /** + * Converts an email message to a calendar item. + * + * @param {calIItemBase} item - The target calIItemBase. + * @param {nsIMsgDBHdr} message - The nsIMsgDBHdr to convert from. + */ + async calendarItemFromMessage(item, message) { + let folder = message.folder; + let msgUri = folder.getUriForMsg(message); + + item.calendar = getSelectedCalendar(); + item.title = message.mime2DecodedSubject; + item.setProperty("URL", `mid:${message.messageId}`); + + cal.dtz.setDefaultStartEndHour(item); + cal.alarms.setDefaultValues(item); + + let content = ""; + await new Promise((resolve, reject) => { + let streamListener = { + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + onDataAvailable(request, inputStream, offset, count) { + let text = folder.getMsgTextFromStream( + inputStream, + message.charset, + count, // bytesToRead + 32768, // maxOutputLen + false, // compressQuotes + true, // stripHTMLTags + {} // out contentType + ); + // If we ever got text, we're good. Ignore further chunks. + content ||= text; + }, + onStartRequest(request) {}, + onStopRequest(request, statusCode) { + if (!Components.isSuccessCode(statusCode)) { + reject(new Error(statusCode)); + } + resolve(); + }, + }; + MailServices.messageServiceFromURI(msgUri).streamMessage( + msgUri, + streamListener, + null, + null, + false, + "", + false + ); + }); + item.setProperty("DESCRIPTION", content); + }, + + /** + * Copy base item properties from aItem to aTarget. This includes properties + * like title, location, description, priority, transparency, attendees, + * categories, calendar, recurrence and possibly more. + * + * @param {object} aItem - The item to copy from. + * @param {object} aTarget - The item to copy to. + */ + copyItemBase(aItem, aTarget) { + const copyProps = ["SUMMARY", "LOCATION", "DESCRIPTION", "URL", "CLASS", "PRIORITY"]; + + for (let prop of copyProps) { + aTarget.setProperty(prop, aItem.getProperty(prop)); + } + + // Attendees + let attendees = aItem.getAttendees(); + for (let attendee of attendees) { + aTarget.addAttendee(attendee.clone()); + } + + // Categories + let categories = aItem.getCategories(); + aTarget.setCategories(categories); + + // Organizer + aTarget.organizer = aItem.organizer ? aItem.organizer.clone() : null; + + // Calendar + aTarget.calendar = getSelectedCalendar(); + + // Recurrence + if (aItem.recurrenceInfo) { + aTarget.recurrenceInfo = aItem.recurrenceInfo.clone(); + aTarget.recurrenceInfo.item = aTarget; + } + }, + + /** + * Creates a task from the passed event. This function copies the base item + * and a few event specific properties (dates, alarms, ...). + * + * @param {object} aEvent - The event to copy from. + * @returns {object} The resulting task. + */ + taskFromEvent(aEvent) { + let item = new CalTodo(); + + this.copyItemBase(aEvent, item); + + // Dates and alarms + if (!aEvent.startDate.isDate && !aEvent.endDate.isDate) { + // Dates + item.entryDate = aEvent.startDate.clone(); + item.dueDate = aEvent.endDate.clone(); + + // Alarms + for (let alarm of aEvent.getAlarms()) { + item.addAlarm(alarm.clone()); + } + item.alarmLastAck = aEvent.alarmLastAck ? aEvent.alarmLastAck.clone() : null; + } + + // Map Status values + let statusMap = { + TENTATIVE: "NEEDS-ACTION", + CONFIRMED: "IN-PROCESS", + CANCELLED: "CANCELLED", + }; + if (aEvent.getProperty("STATUS") in statusMap) { + item.setProperty("STATUS", statusMap[aEvent.getProperty("STATUS")]); + } + return item; + }, + + /** + * Creates an event from the passed task. This function copies the base item + * and a few task specific properties (dates, alarms, ...). If the task has + * no due date, the default event length is used. + * + * @param {object} aTask - The task to copy from. + * @returns {object} The resulting event. + */ + eventFromTask(aTask) { + let item = new CalEvent(); + + this.copyItemBase(aTask, item); + + // Dates and alarms + item.startDate = aTask.entryDate; + if (!item.startDate) { + if (aTask.dueDate) { + item.startDate = aTask.dueDate.clone(); + item.startDate.minute -= Services.prefs.getIntPref("calendar.event.defaultlength", 60); + } else { + item.startDate = cal.dtz.getDefaultStartDate(); + } + } + + item.endDate = aTask.dueDate; + if (!item.endDate) { + // Make the event be the default event length if no due date was + // specified. + item.endDate = item.startDate.clone(); + item.endDate.minute += Services.prefs.getIntPref("calendar.event.defaultlength", 60); + } + + // Alarms + for (let alarm of aTask.getAlarms()) { + item.addAlarm(alarm.clone()); + } + item.alarmLastAck = aTask.alarmLastAck ? aTask.alarmLastAck.clone() : null; + + // Map Status values + let statusMap = { + "NEEDS-ACTION": "TENTATIVE", + COMPLETED: "CONFIRMED", + "IN-PROCESS": "CONFIRMED", + CANCELLED: "CANCELLED", + }; + if (aTask.getProperty("STATUS") in statusMap) { + item.setProperty("STATUS", statusMap[aTask.getProperty("STATUS")]); + } + return item; + }, + }; + + /** + * CalDNDTransferHandler provides a base class for handling drag and drop data + * transfers based on detected mime types. Actual processing of the dropped + * data is left up to CalDNDListener however children of this class mostly + * do some preprocessing first. + * + * The main methods here are the handleDataTransferItem() and handleString() + * methods that initiate transfer from a DataTransferItem or string + * respectively. Whether the data is passed as a DataTransferItem or string + * mostly depends on whether dropped from an external application or + * internally. + * + * @abstract + */ + class CalDNDTransferHandler { + /** + * List of mime types this class handles (Overridden by child class). + * + * @type {string[]} + */ + mimeTypes = []; + + /** + * @param {CalDNDListener} listener - The listener that received the + * original drop event. Most CalDNDTransferHandlers will invoke a method on + * this class once data has been processed. + */ + constructor(listener) { + this.listener = listener; + } + + /** + * Returns true if the handler is able to process any of the given mime types. + * + * @param {string|string[]} mime - The mime type to handle. + * + * @returns {boolean} + */ + willTransfer(mime) { + return Array.isArray(mime) + ? this.mimeTypes.find(type => mime.includes(type)) + : this.mimeTypes.includes(mime); + } + + /** + * Selects the most appropriate type from a list to use with mozGetDataAt(). + * + * @param {string[]} types + * + * @returns {string?} + */ + getMozType(types) { + return types.find(type => this.mimeTypes.includes(type)); + } + + /** + * Overridden by child classes that handle DataTransferItems. By default, no + * processing is done. + * + * @param {DataTransferItem} item + */ + async handleDataTransferItem(item) {} + + /** + * Overridden by child classes that handle string data. By default, no + * processing is done. + * + * @param {string} data + */ + async handleString() {} + } + + /** + * CalDNDMozMessageTransferHandler handles messages dropped from the + * message pane. + */ + class CalDNDMozMessageTransferHandler extends CalDNDTransferHandler { + mimeTypes = ["text/x-moz-message"]; + + /** + * Treats the provided data as a message uri. Invokes the listener's + * onMessageDrop() method with the corresponding message header. + * + * @param {string} data + */ + async handleString(data) { + let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger); + this.listener.onDropMessage(messenger.msgHdrFromURI(data)); + } + } + + /** + * CalDNDAddressTransferHandler handles address book data internally dropped. + */ + class CalDNDAddressTransferHandler extends CalDNDTransferHandler { + mimeTypes = ["text/x-moz-address"]; + + /** + * Invokes the listener's onDropAddress() method. + * + * @param {string} data + */ + async handleString(data) { + this.listener.onDropAddress(data); + } + } + + /** + * CalDNDDefaultTransferHandler serves as a "catch all" and should be included + * last in the list of handlers. + */ + class CalDNDDefaultTransferHandler extends CalDNDTransferHandler { + willTransfer() { + return true; + } + + /** + * If the dropped item is a file, it is treated as an event attachment, + * otherwise it is ignored. + * + * @param {DataTransferItem} item + */ + async handleDataTransferItem(item) { + if (item.kind == "file") { + let path = item.getAsFile().mozFullPath; + if (path) { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(path); + + let uri = Services.io.newFileURI(file); + this.listener.onDropURL(uri); + } + } + } + } + + /** + * CalDNDDirectTransferHandler provides a base class for CalDNDTransferHandlers + * that directly extract the contents of a DataTransferItem for processing. + * + * @abstract + */ + class CalDNDDirectTransferHandler extends CalDNDTransferHandler { + /** + * Extracts the raw string data from a DataTransferItem before passing to + * handleString(). + * + * @param {DataTransferItem} item + */ + async handleDataTransferItem(item) { + if (item.kind == "string") { + let txt = await new Promise(resolve => item.getAsString(resolve)); + await this.handleString(txt); + } else if (item.kind == "file") { + let txt = await item.getAsFile().text(); + await this.handleString(txt); + } + } + } + + /** + * CalDNDICSTransferHandler handles internal or external data in ICS format. + */ + class CalDNDICSTransferHandler extends CalDNDDirectTransferHandler { + mimeTypes = ["text/calendar", "application/x-extension-ics"]; + + /** + * Parses the provided data as an ICS string before invoking the listener's + * onDropItems() method. + * + * @param {string} data + */ + async handleString(data) { + if (AppConstants.platform == "macosx") { + // Mac likes to convert all \r to \n, we need to reverse this. + data = data.replace(/\n\n/g, "\r\n"); + } + + let parser = Cc["@mozilla.org/calendar/ics-parser;1"].createInstance(Ci.calIIcsParser); + parser.parseString(data); + this.listener.onDropItems(parser.getItems().concat(parser.getParentlessItems())); + } + } + + /** + * CalDNDURLTransferHandler handles urls (dropped internally or externally). + */ + class CalDNDURLTransferHandler extends CalDNDDirectTransferHandler { + mimeTypes = ["text/uri-list", "text/x-moz-url"]; + + _icsFilename = /filename=.*\.ics/; + + /** + * Treats the provided data as a url. If we determine it is a url to an + * ICS file, we delegate to the "text/calendar" handler. The listener's + * onDropURL method is invoked otherwise. + * + * @param {string} data + */ + async handleString(data) { + data = data.split("\n")[0]; + if (!data) { + return; + } + + let uri = Services.io.newURI(data); + + // Below we attempt to detect ics files dropped from the message pane's + // attachment list. These will appear as uris rather than file blobs so we + // check the "filename" query parameter for a .ics extension. + if (this._icsFilename.test(uri.query)) { + let url = uri.mutate().setUsername("").setUserPass("").finalize().spec; + + let resp = await fetch(new Request(url, { method: "GET" })); + let txt = await resp.text(); + await this.listener.getHandler("text/calendar").handleString(txt); + } else { + this.listener.onDropURL(uri); + } + } + } + + /** + * CalDNDPlainTextTransferHandler handles text/plain transfers coming mainly + * from internally dropped text. + */ + class CalDNDPlainTextTransferHandler extends CalDNDDirectTransferHandler { + mimeTypes = ["text/plain"]; + + _keyWords = ["VEVENT", "VTODO", "VCALENDAR"]; + + _isICS(data) { + return this._keyWords.some(kwrd => data.includes(kwrd)); + } + + /** + * Treats the data provided as an uri to an .ics file and attempts to parse + * its contents. If we detect calendar data however, we delegate to the + * "text/calendar" handler. + * + * @param {string} data + */ + async handleString(data) { + if (this._isICS(data)) { + this.listener.getHandler("text/calendar").handleString(data); + return; + } + + let droppedUrl = data.split("\n")[0]; + if (!droppedUrl) { + return; + } + + let url = Services.io.newURI(droppedUrl); + + let localFileInstance = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + localFileInstance.initWithPath(url.pathQueryRef); + + let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + inputStream.init(localFileInstance, MODE_RDONLY, parseInt("0444", 8), {}); + + try { + let importer = Cc["@mozilla.org/calendar/import;1?type=ics"].getService(Ci.calIImporter); + let items = importer.importFromStream(inputStream); + this.onDropItems(items); + } finally { + inputStream.close(); + } + } + } + + /** + * This is the base class for calendar drag and drop listeners. + */ + class CalDNDListener { + /** + * Limits the number of items to process from a drop operation. In the + * future, this could be removed in favour of better UI for bulk operations. + * + * @type {number} + */ + maxItemsTransferred = 8; + + /** + * A list of CalDNDTransferHandlers for all of the supported mime types. + * The order of this list is important as it dictates which types will be + * selected first. + * + * @type {CalDNDTransferHandler[]} + */ + mimeHandlers = [ + new CalDNDICSTransferHandler(this), + new CalDNDMozMessageTransferHandler(this), + new CalDNDAddressTransferHandler(this), + new CalDNDURLTransferHandler(this), + new CalDNDPlainTextTransferHandler(this), + new CalDNDDefaultTransferHandler(this), + ]; + + /** + * Provides the most suitable handler for the type or one of the types of a + * list. + * + * @param {string|string[]} mime + * + * @returns {CalDNDTransferHandler} + */ + getHandler(mime) { + return this.mimeHandlers.find(handler => handler.willTransfer(mime)); + } + + /** + * Prevents the browser's default behaviour when an item is dragged over the + * drop target. + * + * @param {Event} event + */ + onDragOver(event) { + event.preventDefault(); + } + + /** + * Handles calendar event items. + * + * @param {calIItemBase[]} items + */ + onDropItems() {} + + /** + * Handles mail messages. + * + * @param {nsIMsgHdr} msgHdr + */ + onDropMessage() {} + + /** + * Handles address book data. + */ + onDropAddress() {} + + /** + * Handles the drop event. The items property of DataTransfer can be + * interpreted differently depending on whether the drop is coming from an + * internal or external source (really its up to whatever is sending the + * data to decide what the transfer entails). + * + * Mozilla seems to treat it as alternative formats for the data being + * sent while external/other applications may only have one data transfer + * item per single thing dropped. The item's interface seems to have + * more accurate mime types than the ones of mozTypesAt() so working with + * those are preferable however not always possible. + * + * This method tries to determine which of the APIs is more appropriate for + * processing the drop. It does that by checking for a source node or a + * difference between length of DataTransfer.items and DataTransfer + * .mozItemCount. + * + * Note: While testing, it was noticed that dragging text from an external + * application shows up erroneously as a file in DataTransfer.items. This is + * dealt with too. + * + * @param {Event} event + */ + async onDrop(event) { + let { dataTransfer } = event; + + // No mozSourceNode means it's an external drop, however if the drop is + // coming from Firefox then we can expect the same behaviour as done + // internally. Generally there may be more DataTransferItems than + // mozItemCount indicates. + let isInternal = + dataTransfer.mozSourceNode || dataTransfer.items.length != dataTransfer.mozItemCount; + + // For the strange case of copied text having the "file" kind, the files + // property will have a length of zero. + let actualFiles = Array.from(dataTransfer.items).filter(i => i.kind == "file").length; + let isExternalText = actualFiles != dataTransfer.files.length; + + if (isInternal || isExternalText) { + await this.onInternalDrop(dataTransfer); + } else { + await this.onExternalDrop(dataTransfer); + } + } + + /** + * This method is intended for use when the drop event originates internally. + * + * @param {DataTransfer} dataTransfer + */ + async onInternalDrop(dataTransfer) { + for (let i = 0; i < dataTransfer.mozItemCount; i++) { + if (i == this.maxItemsTransferred) { + break; + } + + let types = Array.from(dataTransfer.mozTypesAt(i)); + let handler = this.getHandler(types); + let data = dataTransfer.mozGetDataAt(handler.getMozType(types), i); + + if (typeof data == "string") { + await handler.handleString(data); + } + } + } + + /** + * This method is intended for use when the drop event originates externally. + * + * @param {DataTransfer} dataTransfer + */ + async onExternalDrop(dataTransfer) { + let i = 0; + for (let item of dataTransfer.items) { + if (i == this.maxItemsTransferred) { + break; + } + + let handler = this.getHandler(item.type); + await handler.handleDataTransferItem(item, i, dataTransfer); + i++; + } + } + } + + /** + * Drag'n'drop handler for the calendar views. + */ + class CalViewDNDObserver extends CalDNDListener { + wrappedJSObject = this; + + /** + * Gets called in case we're dropping an array of items on one of the + * calendar views. In this case we just try to add these items to the + * currently selected calendar. + * + * @param {calIItemBase[]} items + */ + onDropItems(items) { + let destCal = getSelectedCalendar(); + startBatchTransaction(); + // we fall back explicitly to the popup to ask whether to send a + // notification to participants if required + let extResp = { responseMode: Ci.calIItipItem.USER }; + try { + for (let item of items) { + doTransaction("add", item, destCal, null, null, extResp); + } + } finally { + endBatchTransaction(); + } + } + } + + /** + * Drag'n'drop handler for the 'mail mode'-button. This handler is derived + * from the base handler and just implements specific actions. + */ + class CalMailButtonDNDObserver extends CalDNDListener { + wrappedJSObject = this; + + /** + * Gets called in case we're dropping an array of items on the + * 'mail mode'-button. + * + * @param {calIItemBase[]} items + */ + onDropItems(items) { + if (items && items.length > 0) { + let item = items[0]; + let identity = item.calendar.getProperty("imip.identity"); + let parties = item.getAttendees(); + if (item.organizer) { + parties.push(item.organizer); + } + if (identity) { + // if no identity is defined, the composer will fall back to + // whatever seems suitable - in this case we don't try to remove + // the sender from the recipient list + identity = identity.QueryInterface(Ci.nsIMsgIdentity); + parties = parties.filter(aParty => { + return identity.email != cal.email.getAttendeeEmail(aParty, false); + }); + } + let recipients = cal.email.createRecipientList(parties); + cal.email.sendTo(recipients, item.title, item.getProperty("DESCRIPTION"), identity); + } + } + } + + /** + * Drag'n'drop handler for the 'open calendar tab'-button. This handler is + * derived from the base handler and just implements specific actions. + */ + class CalCalendarButtonObserver extends CalDNDListener { + wrappedJSObject = this; + + /** + * Gets called in case we're dropping an array of items + * on the 'open calendar tab'-button. + * + * @param {calIItemBase[]} items + */ + onDropItems(items) { + for (let item of items) { + let newItem = item; + if (item.isTodo()) { + newItem = itemConversion.eventFromTask(item); + } + createEventWithDialog(null, null, null, null, newItem); + } + } + + /** + * Gets called in case we're dropping a message on the 'open calendar tab'- + * button. In this case we create a new event from the mail. We open the + * default event dialog and just use the subject of the message as the event + * title. + * + * @param {nsIMsgHdr} msgHdr + */ + async onDropMessage(msgHdr) { + let newItem = new CalEvent(); + await itemConversion.calendarItemFromMessage(newItem, msgHdr); + createEventWithDialog(null, null, null, null, newItem); + } + + /** + * Gets called in case we're dropping a uri on the 'open calendar tab'- + * button. + * + * @param {nsIURI} uri + */ + onDropURL(uri) { + let newItem = new CalEvent(); + newItem.calendar = getSelectedCalendar(); + cal.dtz.setDefaultStartEndHour(newItem); + cal.alarms.setDefaultValues(newItem); + let attachment = new CalAttachment(); + attachment.uri = uri; + newItem.addAttachment(attachment); + createEventWithDialog(null, null, null, null, newItem); + } + + /** + * Gets called in case we're dropping addresses on the 'open calendar tab' + * -button. + * + * @param {string} addresses + */ + onDropAddress(addresses) { + let parsedInput = MailServices.headerParser.makeFromDisplayAddress(addresses); + let attendee = new CalAttendee(); + attendee.id = ""; + attendee.rsvp = "TRUE"; + attendee.role = "REQ-PARTICIPANT"; + attendee.participationStatus = "NEEDS-ACTION"; + let attendees = parsedInput + .filter(address => address.name.length > 0) + .map((address, index) => { + // Convert address to attendee. + if (index > 0) { + attendee = attendee.clone(); + } + attendee.id = cal.email.prependMailTo(address.email); + let commonName = null; + if (address.name.length > 0) { + // We remove any double quotes within CN due to bug 1209399. + let name = address.name.replace(/(?:(?:[\\]")|(?:"))/g, ""); + if (address.email != name) { + commonName = name; + } + } + attendee.commonName = commonName; + return attendee; + }); + let newItem = new CalEvent(); + newItem.calendar = getSelectedCalendar(); + cal.dtz.setDefaultStartEndHour(newItem); + cal.alarms.setDefaultValues(newItem); + for (let attendee of attendees) { + newItem.addAttendee(attendee); + } + createEventWithDialog(null, null, null, null, newItem); + } + } + + /** + * Drag'n'drop handler for the 'open tasks tab'-button. This handler is + * derived from the base handler and just implements specific actions. + */ + class CalTaskButtonObserver extends CalDNDListener { + wrappedJSObject = this; + + /** + * Gets called in case we're dropping an array of items on the + * 'open tasks tab'-button. + * + * @param {object} items - An array of items to handle. + */ + onDropItems(items) { + for (let item of items) { + let newItem = item; + if (item.isEvent()) { + newItem = itemConversion.taskFromEvent(item); + } + createTodoWithDialog(null, null, null, newItem); + } + } + + /** + * Gets called in case we're dropping a message on the 'open tasks tab' + * -button. + * + * @param {nsIMsgHdr} msgHdr + */ + async onDropMessage(msgHdr) { + let todo = new CalTodo(); + await itemConversion.calendarItemFromMessage(todo, msgHdr); + createTodoWithDialog(null, null, null, todo); + } + + /** + * Gets called in case we're dropping a uri on the 'open tasks tab'-button. + * + * @param {nsIURI} uri + */ + onDropURL(uri) { + let todo = new CalTodo(); + todo.calendar = getSelectedCalendar(); + cal.dtz.setDefaultStartEndHour(todo); + cal.alarms.setDefaultValues(todo); + let attachment = new CalAttachment(); + attachment.uri = uri; + todo.addAttachment(attachment); + createTodoWithDialog(null, null, null, todo); + } + } + + calendarViewDNDObserver = new CalViewDNDObserver(); + calendarMailButtonDNDObserver = new CalMailButtonDNDObserver(); + calendarCalendarButtonDNDObserver = new CalCalendarButtonObserver(); + calendarTaskButtonDNDObserver = new CalTaskButtonObserver(); +} + +/** + * Invoke a drag session for the passed item. The passed box will be used as a + * source. + * + * @param {object} aItem - The item to drag. + * @param {object} aXULBox - The XUL box to invoke the drag session from. + */ +function invokeEventDragSession(aItem, aXULBox) { + let transfer = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable); + transfer.init(null); + transfer.addDataFlavor("text/calendar"); + + let flavourProvider = { + QueryInterface: ChromeUtils.generateQI(["nsIFlavorDataProvider"]), + + item: aItem, + getFlavorData(aInTransferable, aInFlavor, aOutData) { + if ( + aInFlavor == "application/vnd.x-moz-cal-event" || + aInFlavor == "application/vnd.x-moz-cal-task" + ) { + aOutData.value = aItem; + } else { + cal.ASSERT(false, "error:" + aInFlavor); + } + }, + }; + + if (aItem.isEvent()) { + transfer.addDataFlavor("application/vnd.x-moz-cal-event"); + transfer.setTransferData("application/vnd.x-moz-cal-event", flavourProvider); + } else if (aItem.isTodo()) { + transfer.addDataFlavor("application/vnd.x-moz-cal-task"); + transfer.setTransferData("application/vnd.x-moz-cal-task", flavourProvider); + } + + // Also set some normal data-types, in case we drag into another app + let serializer = Cc["@mozilla.org/calendar/ics-serializer;1"].createInstance( + Ci.calIIcsSerializer + ); + serializer.addItems([aItem]); + + let supportsString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); + supportsString.data = serializer.serializeToString(); + transfer.setTransferData("text/calendar", supportsString); + transfer.setTransferData("text/plain", supportsString); + + let action = Ci.nsIDragService.DRAGDROP_ACTION_MOVE; + let mutArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); + mutArray.appendElement(transfer); + aXULBox.sourceObject = aItem; + try { + cal.dragService.invokeDragSession(aXULBox, null, null, null, mutArray, action); + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + // Pressing Escape on some platforms results in NS_ERROR_FAILURE + // being thrown. Catch this exception, but throw anything else. + throw e; + } + } +} diff --git a/comm/calendar/base/content/calendar-editable-item.js b/comm/calendar/base/content/calendar-editable-item.js new file mode 100644 index 0000000000..e45570c995 --- /dev/null +++ b/comm/calendar/base/content/calendar-editable-item.js @@ -0,0 +1,464 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global MozElements, MozXULElement, onMouseOverItem, invokeEventDragSession */ + +"use strict"; + +// Wrap in a block to prevent leaking to window scope. +{ + var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + /** + * The MozCalendarEditableItem widget is used as a full day event item in the + * Day and Week views of the calendar. It displays the event name, alarm icon + * and the category type color. It gets displayed in the header container of + * the respective view of the calendar. + * + * @augments MozXULElement + */ + class MozCalendarEditableItem extends MozXULElement { + static get inheritedAttributes() { + return { + ".alarm-icons-box": "flashing", + }; + } + constructor() { + super(); + + this.mOccurrence = null; + + this.mSelected = false; + + this.mCalendarView = null; + + this.addEventListener( + "contextmenu", + event => { + // If the middle/right button was used for click just select the item. + if (!this.selected) { + this.select(event); + } + }, + true + ); + + this.addEventListener("click", event => { + if (event.button != 0 || this.mEditing) { + return; + } + + // If the left button was used and the item is already selected + // and there are no multiple items selected start + // the 'single click edit' timeout. Otherwise select the item too. + // Also, check if the calendar is readOnly or we are offline. + + if ( + this.selected && + !(event.ctrlKey || event.metaKey) && + cal.acl.isCalendarWritable(this.mOccurrence.calendar) && + !cal.itip.isInvitation(this.mOccurrence) + ) { + if (this.editingTimer) { + clearTimeout(this.editingTimer); + } + this.editingTimer = setTimeout(() => this.startEditing(), 350); + } else { + this.select(event); + if (!this.closest("richlistitem")) { + event.stopPropagation(); + } + } + }); + + this.addEventListener("dblclick", event => { + if (event.button != 0) { + return; + } + + event.stopPropagation(); + + // Stop 'single click edit' timeout (if started). + if (this.editingTimer) { + clearTimeout(this.editingTimer); + this.editingTimer = null; + } + + if (this.calendarView && this.calendarView.controller) { + let item = event.ctrlKey ? this.mOccurrence.parentItem : this.mOccurrence; + if (Services.prefs.getBoolPref("calendar.events.defaultActionEdit", true)) { + this.calendarView.controller.modifyOccurrence(item); + return; + } + this.calendarView.controller.viewOccurrence(item); + } + }); + + this.addEventListener("mouseover", event => { + if (this.calendarView && this.calendarView.controller) { + event.stopPropagation(); + onMouseOverItem(event); + } + }); + + // We have two event listeners for dragstart. This event listener is for the bubbling phase. + this.addEventListener("dragstart", event => { + if (document.monthDragEvent?.localName == "calendar-event-box") { + return; + } + let item = this.occurrence; + let isInvitation = + item.calendar instanceof Ci.calISchedulingSupport && item.calendar.isInvitation(item); + if ( + !cal.acl.isCalendarWritable(item.calendar) || + !cal.acl.userCanModifyItem(item) || + isInvitation + ) { + return; + } + if (!this.selected) { + this.select(event); + } + invokeEventDragSession(item, this); + }); + } + + connectedCallback() { + if (this.delayConnectedCallback() || this.hasChildNodes()) { + return; + } + this.appendChild( + MozXULElement.parseXULToFragment(` + + + + + + + `) + ); + + this.classList.add("calendar-color-box", "calendar-item-container"); + + // We have two event listeners for dragstart. This event listener is for the capturing phase + // where we are setting up the document.monthDragEvent which will be used in the event listener + // in the bubbling phase. + this.addEventListener( + "dragstart", + event => { + document.monthDragEvent = this; + }, + true + ); + + this.style.pointerEvents = "auto"; + this.setAttribute("tooltip", "itemTooltip"); + this.setAttribute("tabindex", "-1"); + this.addEventNameTextboxListener(); + this.initializeAttributeInheritance(); + } + + set parentBox(val) { + this.mParentBox = val; + } + + get parentBox() { + return this.mParentBox; + } + + set selected(val) { + if (val && !this.mSelected) { + this.mSelected = true; + this.setAttribute("selected", "true"); + this.focus(); + } else if (!val && this.mSelected) { + this.mSelected = false; + this.removeAttribute("selected"); + this.blur(); + } + } + + get selected() { + return this.mSelected; + } + + set calendarView(val) { + this.mCalendarView = val; + } + + get calendarView() { + return this.mCalendarView; + } + + set occurrence(val) { + this.mOccurrence = val; + this.setEditableLabel(); + this.setLocationLabel(); + this.setCSSClasses(); + } + + get occurrence() { + return this.mOccurrence; + } + + get eventNameLabel() { + return this.querySelector(".event-name-label"); + } + + get eventNameTextbox() { + return this.querySelector(".event-name-input"); + } + + addEventNameTextboxListener() { + let stopPropagationIfEditing = event => { + if (this.mEditing) { + event.stopPropagation(); + } + }; + // While editing, single click positions cursor, so don't propagate. + this.eventNameTextbox.onclick = stopPropagationIfEditing; + // While editing, double click selects words, so don't propagate. + this.eventNameTextbox.ondblclick = stopPropagationIfEditing; + // While editing, don't propagate mousedown/up (selects calEvent). + this.eventNameTextbox.onmousedown = stopPropagationIfEditing; + this.eventNameTextbox.onmouseup = stopPropagationIfEditing; + this.eventNameTextbox.onblur = () => { + this.stopEditing(true); + }; + this.eventNameTextbox.onkeypress = event => { + if (event.key == "Enter") { + // Save on enter. + this.stopEditing(true); + } else if (event.key == "Escape") { + // Abort on escape. + this.stopEditing(false); + } + }; + } + + setEditableLabel() { + let label = this.eventNameLabel; + let item = this.mOccurrence; + label.textContent = item.title + ? item.title.replace(/\n/g, " ") + : cal.l10n.getCalString("eventUntitled"); + } + + setLocationLabel() { + let locationLabel = this.querySelector(".location-desc"); + let location = this.mOccurrence.getProperty("LOCATION"); + let showLocation = Services.prefs.getBoolPref("calendar.view.showLocation", false); + + locationLabel.textContent = showLocation && location ? location : ""; + locationLabel.hidden = !showLocation || !location; + } + + setCSSClasses() { + let item = this.mOccurrence; + let cssSafeId = cal.view.formatStringForCSSRule(item.calendar.id); + this.style.setProperty("--item-backcolor", `var(--calendar-${cssSafeId}-backcolor)`); + this.style.setProperty("--item-forecolor", `var(--calendar-${cssSafeId}-forecolor)`); + let categoriesBox = this.querySelector(".calendar-category-box"); + + let categoriesArray = item.getCategories().map(cal.view.formatStringForCSSRule); + // Find the first category with a colour. + let firstCategory = categoriesArray.find( + category => Services.prefs.getStringPref("calendar.category.color." + category, "") != "" + ); + if (firstCategory) { + categoriesBox.hidden = false; + categoriesBox.style.backgroundColor = `var(--category-${firstCategory}-color)`; + } else { + categoriesBox.hidden = true; + } + + // Add alarm icons as needed. + let alarms = item.getAlarms(); + if (alarms.length && Services.prefs.getBoolPref("calendar.alarms.indicator.show", true)) { + let iconsBox = this.querySelector(".alarm-icons-box"); + // Set suppressed status on the icons box. + iconsBox.toggleAttribute("suppressed", item.calendar.getProperty("suppressAlarms")); + + cal.alarms.addReminderImages(iconsBox, alarms); + } + + // Item classification / privacy. + let classificationIcon = this.querySelector(".item-classification-icon"); + if (classificationIcon) { + switch (item.privacy) { + case "PRIVATE": + classificationIcon.setAttribute( + "src", + "chrome://calendar/skin/shared/icons/private.svg" + ); + // Set the alt attribute. + document.l10n.setAttributes( + classificationIcon, + "calendar-editable-item-privacy-icon-private" + ); + break; + case "CONFIDENTIAL": + classificationIcon.setAttribute( + "src", + "chrome://calendar/skin/shared/icons/confidential.svg" + ); + // Set the alt attribute. + document.l10n.setAttributes( + classificationIcon, + "calendar-editable-item-privacy-icon-confidential" + ); + break; + default: + classificationIcon.removeAttribute("src"); + classificationIcon.removeAttribute("data-l10n-id"); + classificationIcon.setAttribute("alt", ""); + break; + } + } + + let recurrenceIcon = this.querySelector(".item-recurrence-icon"); + if (item.parentItem != item && item.parentItem.recurrenceInfo) { + if (item.parentItem.recurrenceInfo.getExceptionFor(item.recurrenceId)) { + recurrenceIcon.setAttribute( + "src", + "chrome://messenger/skin/icons/new/recurrence-exception.svg" + ); + document.l10n.setAttributes( + recurrenceIcon, + "calendar-editable-item-recurrence-exception" + ); + } else { + recurrenceIcon.setAttribute("src", "chrome://messenger/skin/icons/new/recurrence.svg"); + document.l10n.setAttributes(recurrenceIcon, "calendar-editable-item-recurrence"); + } + recurrenceIcon.hidden = false; + } else { + recurrenceIcon.removeAttribute("src"); + recurrenceIcon.removeAttribute("data-l10n-id"); + recurrenceIcon.setAttribute("alt", ""); + recurrenceIcon.hidden = true; + } + + // Event type specific properties. + if (item.isEvent() && item.startDate.isDate) { + this.setAttribute("allday", "true"); + } + if (item.isTodo()) { + let icon = this.querySelector(".item-type-icon"); + if (cal.item.getProgressAtom(item) === "completed") { + icon.setAttribute("src", "chrome://calendar/skin/shared/todo-complete.svg"); + document.l10n.setAttributes(icon, "calendar-editable-item-todo-icon-completed-task"); + } else { + icon.setAttribute("src", "chrome://calendar/skin/shared/todo.svg"); + document.l10n.setAttributes(icon, "calendar-editable-item-todo-icon-task"); + } + } + + if (this.calendarView && item.hashId in this.calendarView.mFlashingEvents) { + this.setAttribute("flashing", "true"); + } + + if (alarms.length) { + this.setAttribute("alarm", "true"); + } + + // Priority. + if (item.priority > 0 && item.priority < 5) { + this.setAttribute("priority", "high"); + } else if (item.priority > 5 && item.priority < 10) { + this.setAttribute("priority", "low"); + } + + // Status attribute. + if (item.status) { + this.setAttribute("status", item.status.toUpperCase()); + } + + // Item class. + if (item.hasProperty("CLASS")) { + this.setAttribute("itemclass", item.getProperty("CLASS")); + } + + // Calendar name. + this.setAttribute("calendar", item.calendar.name.toLowerCase()); + + // Invitation. + if (cal.itip.isInvitation(item)) { + this.setAttribute( + "invitation-status", + cal.itip.getInvitedAttendee(item).participationStatus + ); + } + } + + startEditing() { + this.editingTimer = null; + this.mOriginalTextLabel = this.mOccurrence.title; + + this.eventNameLabel.hidden = true; + + this.mEditing = true; + + this.eventNameTextbox.value = this.mOriginalTextLabel; + this.eventNameTextbox.hidden = false; + this.eventNameTextbox.focus(); + } + + get isEditing() { + return this.mEditing || false; + } + + select(event) { + if (!this.calendarView) { + return; + } + let items = this.calendarView.mSelectedItems.slice(); + if (event.ctrlKey || event.metaKey) { + if (this.selected) { + let pos = items.indexOf(this.mOccurrence); + items.splice(pos, 1); + } else { + items.push(this.mOccurrence); + } + } else { + items = [this.mOccurrence]; + } + this.calendarView.setSelectedItems(items); + } + + stopEditing(saveChanges) { + if (!this.mEditing) { + return; + } + + this.mEditing = false; + + if (saveChanges && this.eventNameTextbox.value != this.mOriginalTextLabel) { + this.calendarView.controller.modifyOccurrence( + this.mOccurrence, + null, + null, + this.eventNameTextbox.value || cal.l10n.getCalString("eventUntitled") + ); + + // Note that as soon as we do the modifyItem, this element ceases to exist, + // so don't bother trying to modify anything further here! ('this' exists, + // because it's being kept alive, but our child content etc. is all gone). + return; + } + + this.eventNameTextbox.hidden = true; + this.eventNameLabel.hidden = false; + } + } + + MozElements.MozCalendarEditableItem = MozCalendarEditableItem; + + customElements.define("calendar-editable-item", MozCalendarEditableItem); +} diff --git a/comm/calendar/base/content/calendar-extract.js b/comm/calendar/base/content/calendar-extract.js new file mode 100644 index 0000000000..de021cb042 --- /dev/null +++ b/comm/calendar/base/content/calendar-extract.js @@ -0,0 +1,266 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals getMessagePaneBrowser, addMenuItem, getSelectedCalendar + createEventWithDialog*/ + +var { Extractor } = ChromeUtils.import("resource:///modules/calendar/calExtract.jsm"); +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); +var { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + +XPCOMUtils.defineLazyModuleGetters(this, { + CalEvent: "resource:///modules/CalEvent.jsm", + CalTodo: "resource:///modules/CalTodo.jsm", +}); + +XPCOMUtils.defineLazyGetter(this, "extractService", () => { + const { CalExtractParserService } = ChromeUtils.import( + "resource:///modules/calendar/extract/CalExtractParserService.jsm" + ); + return new CalExtractParserService(); +}); + +var calendarExtract = { + onShowLocaleMenu(target) { + let localeList = document.getElementById(target.id); + let langs = []; + let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIXULChromeRegistry) + .QueryInterface(Ci.nsIToolkitChromeRegistry); + let langRegex = /^(([^-]+)-*(.*))$/; + + for (let locale of chrome.getLocalesForPackage("calendar")) { + let localeParts = langRegex.exec(locale); + let langName = localeParts[2]; + + try { + langName = cal.l10n.getAnyString("global", "languageNames", langName); + } catch (ex) { + // If no language name is found that is ok, keep the technical term + } + + let label = cal.l10n.getCalString("extractUsing", [langName]); + if (localeParts[3] != "") { + label = cal.l10n.getCalString("extractUsingRegion", [langName, localeParts[3]]); + } + + langs.push([label, localeParts[1]]); + } + + // sort + let pref = "calendar.patterns.last.used.languages"; + let lastUsedLangs = Services.prefs.getStringPref(pref, ""); + + langs.sort((a, b) => { + let idx_a = lastUsedLangs.indexOf(a[1]); + let idx_b = lastUsedLangs.indexOf(b[1]); + + if (idx_a == -1 && idx_b == -1) { + return a[0].localeCompare(b[0]); + } else if (idx_a != -1 && idx_b != -1) { + return idx_a - idx_b; + } else if (idx_a == -1) { + return 1; + } + return -1; + }); + while (localeList.lastChild) { + localeList.lastChild.remove(); + } + + for (let lang of langs) { + addMenuItem(localeList, lang[0], lang[1], null); + } + }, + + extractWithLocale(event, isEvent) { + event.stopPropagation(); + let locale = event.target.value; + this.extractFromEmail(null, isEvent, true, locale); + }, + + async extractFromEmail(message, isEvent, fixedLang, fixedLocale) { + let folder = message.folder; + let title = message.mime2DecodedSubject; + + let content = ""; + await new Promise((resolve, reject) => { + let listener = { + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + onDataAvailable(request, inputStream, offset, count) { + let text = folder.getMsgTextFromStream( + inputStream, + message.charset, + count, // bytesToRead + 32768, // maxOutputLen + false, // compressQuotes + true, // stripHTMLTags + {} // out contentType + ); + // If we ever got text, we're good. Ignore further chunks. + content ||= text; + }, + onStartRequest(request) {}, + onStopRequest(request, statusCode) { + if (!Components.isSuccessCode(statusCode)) { + reject(new Error(statusCode)); + } + resolve(); + }, + }; + let uri = message.folder.getUriForMsg(message); + MailServices.messageServiceFromURI(uri).streamMessage(uri, listener, null, null, false, ""); + }); + + cal.LOG("[calExtract] Original email content: \n" + title + "\r\n" + content); + let date = new Date(message.date / 1000); + let time = new Date().getTime(); + + let item = isEvent ? new CalEvent() : new CalTodo(); + item.title = message.mime2DecodedSubject; + item.calendar = getSelectedCalendar(); + item.setProperty("DESCRIPTION", content); + item.setProperty("URL", `mid:${message.messageId}`); + cal.dtz.setDefaultStartEndHour(item); + cal.alarms.setDefaultValues(item); + let tabmail = document.getElementById("tabmail"); + let messagePaneBrowser = + tabmail?.currentTabInfo.chromeBrowser.contentWindow.visibleMessagePaneBrowser?.() || + tabmail?.currentAboutMessage?.getMessagePaneBrowser() || + document.getElementById("messageBrowser")?.contentWindow?.getMessagePaneBrowser(); + let sel = messagePaneBrowser?.contentWindow?.getSelection(); + // Check if there's an iframe with a selection (e.g. Thunderbird Conversations) + if (sel && sel.type !== "Range") { + try { + sel = messagePaneBrowser?.contentDocument + .querySelector("iframe") + .contentDocument.getSelection(); + } catch (ex) { + // If Thunderbird Conversations is not installed that is fine, + // we will just have an empty or null selection. + } + } + + let guessed; + let endGuess; + let extractor; + let collected = []; + let useService = Services.prefs.getBoolPref("calendar.extract.service.enabled"); + if (useService) { + let result = extractService.extract(content, { now: date }); + if (!result) { + useService = false; + } else { + guessed = result.startTime; + endGuess = result.endTime; + } + } + + if (!useService) { + let locale = Services.locale.requestedLocale; + let dayStart = Services.prefs.getIntPref("calendar.view.daystarthour", 6); + if (fixedLang) { + extractor = new Extractor(fixedLocale, dayStart); + } else { + extractor = new Extractor(locale, dayStart, false); + } + collected = extractor.extract(title, content, date, sel); + } + + // if we only have email date then use default start and end + if (!useService && collected.length <= 1) { + cal.LOG("[calExtract] Date and time information was not found in email/selection."); + createEventWithDialog(null, null, null, null, item); + } else { + if (!useService) { + guessed = extractor.guessStart(!isEvent); + endGuess = extractor.guessEnd(guessed, !isEvent); + } + let allDay = (guessed.hour == null || guessed.minute == null) && isEvent; + + if (isEvent) { + if (guessed.year != null) { + item.startDate.year = guessed.year; + } + if (guessed.month != null) { + item.startDate.month = guessed.month - 1; + } + if (guessed.day != null) { + item.startDate.day = guessed.day; + } + if (guessed.hour != null) { + item.startDate.hour = guessed.hour; + } + if (guessed.minute != null) { + item.startDate.minute = guessed.minute; + } + + item.endDate = item.startDate.clone(); + item.endDate.minute += Services.prefs.getIntPref("calendar.event.defaultlength", 60); + + if (endGuess.year != null) { + item.endDate.year = endGuess.year; + } + if (endGuess.month != null) { + item.endDate.month = endGuess.month - 1; + } + if (endGuess.day != null) { + item.endDate.day = endGuess.day; + if (allDay) { + item.endDate.day++; + } + } + if (endGuess.hour != null) { + item.endDate.hour = endGuess.hour; + } + if (endGuess.minute != null) { + item.endDate.minute = endGuess.minute; + } + } else { + let dtz = cal.dtz.defaultTimezone; + let dueDate = new Date(); + // set default + dueDate.setHours(0); + dueDate.setMinutes(0); + dueDate.setSeconds(0); + + if (endGuess.year != null) { + dueDate.setYear(endGuess.year); + } + if (endGuess.month != null) { + dueDate.setMonth(endGuess.month - 1); + } + if (endGuess.day != null) { + dueDate.setDate(endGuess.day); + } + if (endGuess.hour != null) { + dueDate.setHours(endGuess.hour); + } + if (endGuess.minute != null) { + dueDate.setMinutes(endGuess.minute); + } + + cal.item.setItemProperty(item, "entryDate", cal.dtz.jsDateToDateTime(date, dtz)); + if (endGuess.year != null) { + cal.item.setItemProperty(item, "dueDate", cal.dtz.jsDateToDateTime(dueDate, dtz)); + } + } + + // if time not guessed set allday for events + if (allDay) { + createEventWithDialog(null, null, null, null, item, true); + } else { + createEventWithDialog(null, null, null, null, item); + } + } + + let timeSpent = new Date().getTime() - time; + cal.LOG( + "[calExtract] Total time spent for conversion (including loading of dictionaries): " + + timeSpent + + "ms" + ); + }, +}; diff --git a/comm/calendar/base/content/calendar-invitation-display.js b/comm/calendar/base/content/calendar-invitation-display.js new file mode 100644 index 0000000000..ed560d02ed --- /dev/null +++ b/comm/calendar/base/content/calendar-invitation-display.js @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals gMessageListeners, calImipBar */ + +// Wrap in a block to prevent leaking to window scope. +{ + const { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + + /** + * CalInvitationDisplay is the controller responsible for the display of the + * invitation panel when an email contains an embedded invitation. + */ + const CalInvitationDisplay = { + /** + * The itipItem currently displayed. + * + * @type {calIItipItem} + */ + currentItipItem: null, + + /** + * The XUL element that wraps the invitation. + * + * @type {XULElement} + */ + container: null, + + /** + * The node the invitation details are rendered into. + * + * @type {HTMLElement} + */ + display: null, + + /** + * The element that displays the message body. This is hidden + * when the invitation details are displayed. + */ + body: null, + + /** + * Creates a new instance and sets up listeners. + */ + init() { + this.container = document.getElementById("calendarInvitationDisplayContainer"); + this.display = document.getElementById("calendarInvitationDisplay"); + this.body = document.getElementById("messagepane"); + + window.addEventListener("onItipItemCreation", this); + window.addEventListener("onItipItemActionFinished", this); + window.addEventListener("messagepane-unloaded", this); + document.getElementById("msgHeaderView").addEventListener("message-header-pane-hidden", this); + gMessageListeners.push(this); + }, + + /** + * Renders the panel with invitation details when "onItipItemCreation" is + * received. + * + * @param {Event} evt + */ + handleEvent(evt) { + switch (evt.type) { + case "DOMContentLoaded": + this.init(); + break; + case "onItipItemCreation": + case "onItipItemActionFinished": + this.show(evt.detail); + break; + case "messagepane-unloaded": + case "message-header-pane-hidden": + this.hide(); + break; + case "calendar-invitation-panel-action": + if (evt.detail.type == "update") { + calImipBar.executeAction(); + } else { + calImipBar.executeAction(evt.detail.type.toUpperCase()); + } + break; + default: + break; + } + }, + + /** + * Hide the invitation display each time a new message to display is + * detected. If the message contains an invitation it will be displayed + * in the "onItipItemCreation" handler. + */ + onStartHeaders() { + this.hide(); + }, + + /** + * Called by messageHeaderSink. + */ + onEndHeaders() {}, + + /** + * Displays the invitation display with the data from the provided + * calIItipItem. + * + * @param {calIItipItem} itipItem + */ + async show(itipItem) { + this.currentItipItem = itipItem; + this.display.replaceChildren(); + + let [, rc, actionFunc, foundItems] = await new Promise(resolve => + cal.itip.processItipItem(itipItem, (targetItipItem, rc, actionFunc, foundItems) => + resolve([targetItipItem, rc, actionFunc, foundItems]) + ) + ); + + if (this.currentItipItem != itipItem || !Components.isSuccessCode(rc)) { + return; + } + + let [item] = itipItem.getItemList(); + let [foundItem] = foundItems; + let panel = document.createElement("calendar-invitation-panel"); + panel.addEventListener("calendar-invitation-panel-action", this); + + let method = actionFunc ? actionFunc.method : itipItem.receivedMethod; + switch (method) { + case "REQUEST:UPDATE": + panel.mode = panel.constructor.MODE_UPDATE_MAJOR; + break; + case "REQUEST:UPDATE-MINOR": + panel.mode = panel.constructor.MODE_UPDATE_MINOR; + break; + case "REQUEST": + panel.mode = foundItem + ? panel.constructor.MODE_ALREADY_PROCESSED + : panel.constructor.MODE_NEW; + break; + case "CANCEL": + panel.mode = foundItem + ? panel.constructor.MODE_CANCELLED + : panel.constructor.MODE_CANCELLED_NOT_FOUND; + break; + default: + panel.mode = panel.mode = panel.constructor.MODE_NEW; + break; + } + panel.foundItem = foundItem; + panel.item = item; + this.display.appendChild(panel); + this.body.hidden = true; + this.container.hidden = false; + }, + + /** + * Removes the invitation display from view, resetting any changes made + * to the container and message pane. + */ + hide() { + this.container.hidden = true; + this.display.replaceChildren(); + this.body.hidden = false; + }, + }; + + window.addEventListener("DOMContentLoaded", CalInvitationDisplay, { once: true }); +} diff --git a/comm/calendar/base/content/calendar-invitations-manager.js b/comm/calendar/base/content/calendar-invitations-manager.js new file mode 100644 index 0000000000..9a758c4c49 --- /dev/null +++ b/comm/calendar/base/content/calendar-invitations-manager.js @@ -0,0 +1,385 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +var { CalReadableStreamFactory } = ChromeUtils.import( + "resource:///modules/CalReadableStreamFactory.jsm" +); + +/* exported openInvitationsDialog, setUpInvitationsManager, + * tearDownInvitationsManager + */ + +var gInvitationsManager = null; + +/** + * Return a cached instance of the invitations manager + * + * @returns {InvitationsManager} The invitations manager instance. + */ +function getInvitationsManager() { + if (!gInvitationsManager) { + gInvitationsManager = new InvitationsManager(); + } + return gInvitationsManager; +} + +// Listeners, observers, set up, tear down, opening dialog, etc. This code kept +// separate from the InvitationsManager class itself for separation of concerns. + +// == invitations link +const FIRST_DELAY_STARTUP = 100; +const FIRST_DELAY_RESCHEDULE = 100; +const FIRST_DELAY_REGISTER = 10000; +const FIRST_DELAY_UNREGISTER = 0; + +var gInvitationsCalendarManagerObserver = { + mStoredThis: this, + QueryInterface: ChromeUtils.generateQI(["calICalendarManagerObserver"]), + + onCalendarRegistered(aCalendar) { + this.mStoredThis.rescheduleInvitationsUpdate(FIRST_DELAY_REGISTER); + }, + + onCalendarUnregistering(aCalendar) { + this.mStoredThis.rescheduleInvitationsUpdate(FIRST_DELAY_UNREGISTER); + }, + + onCalendarDeleting(aCalendar) {}, +}; + +function scheduleInvitationsUpdate(firstDelay) { + getInvitationsManager().scheduleInvitationsUpdate(firstDelay); +} + +function rescheduleInvitationsUpdate(firstDelay) { + getInvitationsManager().cancelInvitationsUpdate(); + scheduleInvitationsUpdate(firstDelay); +} + +function openInvitationsDialog() { + getInvitationsManager().cancelInvitationsUpdate(); + getInvitationsManager().openInvitationsDialog(); +} + +function setUpInvitationsManager() { + scheduleInvitationsUpdate(FIRST_DELAY_STARTUP); + cal.manager.addObserver(gInvitationsCalendarManagerObserver); +} + +function tearDownInvitationsManager() { + cal.manager.removeObserver(gInvitationsCalendarManagerObserver); +} + +/** + * The invitations manager class constructor + * + * XXX do we really need this to be an instance? + * + * @class + */ +function InvitationsManager() { + this.mItemList = []; + this.mStartDate = null; + this.mTimer = null; + + window.addEventListener("unload", () => { + // Unload handlers get removed automatically + this.cancelInvitationsUpdate(); + }); +} + +InvitationsManager.prototype = { + mItemList: null, + mStartDate: null, + mTimer: null, + mPendingRequests: null, + + /** + * Schedule an update for the invitations manager asynchronously. + * + * @param firstDelay The timeout before the operation should start. + */ + scheduleInvitationsUpdate(firstDelay) { + this.cancelInvitationsUpdate(); + + this.mTimer = setTimeout(async () => { + if (Services.prefs.getBoolPref("calendar.invitations.autorefresh.enabled", true)) { + this.mTimer = setInterval( + async () => this._doInvitationsUpdate(), + Services.prefs.getIntPref("calendar.invitations.autorefresh.timeout", 3) * 60000 + ); + } + await this._doInvitationsUpdate(); + }, firstDelay); + }, + + async _doInvitationsUpdate() { + let items; + try { + items = await cal.iterate.streamToArray(this.getInvitations()); + } catch (e) { + cal.ERROR(e); + } + this.toggleInvitationsPanel(items); + }, + + /** + * Toggles the display of the invitations panel in the status bar depending + * on the number of invitation items found. + * + * @param {calIItemBase[]?} items - The invitations found, if empty or not + * provided, the panel will not be displayed. + */ + toggleInvitationsPanel(items) { + let invitationsBox = document.getElementById("calendar-invitations-panel"); + if (items) { + let count = items.length; + let value = cal.l10n.getLtnString("invitationsLink.label", [count]); + document.getElementById("calendar-invitations-label").value = value; + if (count) { + invitationsBox.removeAttribute("hidden"); + return; + } + } + + invitationsBox.setAttribute("hidden", "true"); + }, + + /** + * Cancel pending any pending invitations update. + */ + cancelInvitationsUpdate() { + clearTimeout(this.mTimer); + }, + + /** + * Cancel any pending queries for invitations. + */ + async cancelPendingRequests() { + return this.mPendingRequests && this.mPendingRequests.cancel(); + }, + + /** + * Retrieve invitations from all calendars. Notify all passed + * operation listeners. + * + * @returns {ReadableStream} + */ + getInvitations() { + this.updateStartDate(); + this.deleteAllItems(); + + let streams = []; + for (let calendar of cal.manager.getCalendars()) { + if (!cal.acl.isCalendarWritable(calendar) || calendar.getProperty("disabled")) { + continue; + } + + // temporary hack unless calCachedCalendar supports REQUEST_NEEDS_ACTION filter: + calendar = calendar.getProperty("cache.uncachedCalendar"); + if (!calendar) { + continue; + } + + let endDate = this.mStartDate.clone(); + endDate.year += 1; + streams.push( + calendar.getItems( + Ci.calICalendar.ITEM_FILTER_REQUEST_NEEDS_ACTION | + Ci.calICalendar.ITEM_FILTER_TYPE_ALL | + // we need to retrieve by occurrence to properly filter exceptions, + // should be fixed with bug 416975 + Ci.calICalendar.ITEM_FILTER_CLASS_OCCURRENCES, + 0, + this.mStartDate, + endDate /* we currently cannot pass null here, because of bug 416975 */ + ) + ); + } + + let self = this; + let mHandledItems = {}; + return CalReadableStreamFactory.createReadableStream({ + async start(controller) { + await self.cancelPendingRequests(); + + self.mPendingRequests = cal.iterate.streamValues( + CalReadableStreamFactory.createCombinedReadableStream(streams) + ); + + for await (let items of self.mPendingRequests) { + for (let item of items) { + // we need to retrieve by occurrence to properly filter exceptions, + // should be fixed with bug 416975 + item = item.parentItem; + let hid = item.hashId; + if (!mHandledItems[hid]) { + mHandledItems[hid] = true; + self.addItem(item); + } + } + } + + self.mItemList.sort((a, b) => { + return a.startDate.compare(b.startDate); + }); + + controller.enqueue(self.mItemList.slice()); + controller.close(); + }, + close() { + self.mPendingRequests = null; + }, + }); + }, + + /** + * Open the invitations dialog, non-modal. + * + * XXX Passing these listeners in instead of keeping them in the window + * sounds fishy to me. Maybe there is a more encapsulated solution. + */ + openInvitationsDialog() { + let args = {}; + args.queue = []; + args.finishedCallBack = () => this.scheduleInvitationsUpdate(FIRST_DELAY_RESCHEDULE); + args.invitationsManager = this; + // the dialog will reset this to auto when it is done loading + window.setCursor("wait"); + // open the dialog + window.openDialog( + "chrome://calendar/content/calendar-invitations-dialog.xhtml", + "_blank", + "chrome,titlebar,resizable", + args + ); + }, + + /** + * Process the passed job queue. A job is an object that consists of an + * action, a newItem and and oldItem. This processor only takes "modify" + * operations into account. + * + * @param queue The array of objects to process. + */ + async processJobQueue(queue) { + // TODO: undo/redo + for (let i = 0; i < queue.length; i++) { + let job = queue[i]; + let oldItem = job.oldItem; + let newItem = job.newItem; + switch (job.action) { + case "modify": + let item = await newItem.calendar.modifyItem(newItem, oldItem); + cal.itip.checkAndSend(Ci.calIOperationListener.MODIFY, item, oldItem); + this.deleteItem(item); + this.addItem(item); + break; + default: + break; + } + } + }, + + /** + * Checks if the internal item list contains the given item + * XXXdbo Please document these correctly. + * + * @param item The item to look for. + * @returns A boolean value indicating if the item was found. + */ + hasItem(item) { + let hid = item.hashId; + return this.mItemList.some(item_ => hid == item_.hashId); + }, + + /** + * Adds an item to the internal item list. + * XXXdbo Please document these correctly. + * + * @param item The item to add. + */ + addItem(item) { + let recInfo = item.recurrenceInfo; + if (recInfo && !cal.itip.isOpenInvitation(item)) { + // scan exceptions: + let ids = recInfo.getExceptionIds(); + for (let id of ids) { + let ex = recInfo.getExceptionFor(id); + if (ex && this.validateItem(ex) && !this.hasItem(ex)) { + this.mItemList.push(ex); + } + } + } else if (this.validateItem(item) && !this.hasItem(item)) { + this.mItemList.push(item); + } + }, + + /** + * Removes an item from the internal item list + * XXXdbo Please document these correctly. + * + * @param item The item to remove. + */ + deleteItem(item) { + let id = item.id; + this.mItemList.filter(item_ => id != item_.id); + }, + + /** + * Remove all items from the internal item list + * XXXdbo Please document these correctly. + */ + deleteAllItems() { + this.mItemList = []; + }, + + /** + * Helper function to create a start date to search from. This date is the + * current time with hour/minute/second set to zero. + * + * @returns Potential start date. + */ + getStartDate() { + let date = cal.dtz.now(); + date.second = 0; + date.minute = 0; + date.hour = 0; + return date; + }, + + /** + * Updates the start date for the invitations manager to the date returned + * from this.getStartDate(), unless the previously existing start date is + * the same or after what getStartDate() returned. + */ + updateStartDate() { + if (this.mStartDate) { + let startDate = this.getStartDate(); + if (startDate.compare(this.mStartDate) > 0) { + this.mStartDate = startDate; + } + } else { + this.mStartDate = this.getStartDate(); + } + }, + + /** + * Checks if the item is valid for the invitation manager. Checks if the + * item is in the range of the invitation manager and if the item is a valid + * invitation. + * + * @param item The item to check + * @returns A boolean indicating if the item is a valid invitation. + */ + validateItem(item) { + if (item.calendar instanceof Ci.calISchedulingSupport && !item.calendar.isInvitation(item)) { + return false; // exclude if organizer has invited himself + } + let start = item[cal.dtz.startDateProp(item)] || item[cal.dtz.endDateProp(item)]; + return cal.itip.isOpenInvitation(item) && start.compare(this.mStartDate) >= 0; + }, +}; diff --git a/comm/calendar/base/content/calendar-keys.inc.xhtml b/comm/calendar/base/content/calendar-keys.inc.xhtml new file mode 100644 index 0000000000..572a62d88e --- /dev/null +++ b/comm/calendar/base/content/calendar-keys.inc.xhtml @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + + + + + + + diff --git a/comm/calendar/base/content/calendar-management.js b/comm/calendar/base/content/calendar-management.js new file mode 100644 index 0000000000..351c44b184 --- /dev/null +++ b/comm/calendar/base/content/calendar-management.js @@ -0,0 +1,721 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals sortCalendarArray, gDataMigrator, calendarUpdateNewItemsCommand, currentView */ + +/* exported promptDeleteCalendar, loadCalendarManager, unloadCalendarManager, + * calendarListTooltipShowing, calendarListSetupContextMenu, + * ensureCalendarVisible, toggleCalendarVisible, showAllCalendars, + * showOnlyCalendar, calendarOfflineManager, openLocalCalendar, + */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +/** + * Get this window's currently selected calendar. + * + * @returns The currently selected calendar. + */ +function getSelectedCalendar() { + return cal.view.getCompositeCalendar(window).defaultCalendar; +} + +/** + * Deletes the passed calendar, prompting the user if he really wants to do + * this. If there is only one calendar left, no calendar is removed and the user + * is not prompted. + * + * @param aCalendar The calendar to delete. + */ +function promptDeleteCalendar(aCalendar) { + let calendars = cal.manager.getCalendars(); + if (calendars.length <= 1) { + // If this is the last calendar, don't delete it. + return; + } + + let modes = new Set(aCalendar.getProperty("capabilities.removeModes") || ["unsubscribe"]); + let title = cal.l10n.getCalString("removeCalendarTitle"); + + let textKey, b0text, b2text; + let removeFlags = 0; + let promptFlags = + Ci.nsIPromptService.BUTTON_POS_0 * Ci.nsIPromptService.BUTTON_TITLE_IS_STRING + + Ci.nsIPromptService.BUTTON_POS_1 * Ci.nsIPromptService.BUTTON_TITLE_CANCEL; + + if (modes.has("delete") && !modes.has("unsubscribe")) { + textKey = "removeCalendarMessageDelete"; + promptFlags += Ci.nsIPromptService.BUTTON_DELAY_ENABLE; + b0text = cal.l10n.getCalString("removeCalendarButtonDelete"); + } else if (modes.has("delete")) { + textKey = "removeCalendarMessageDeleteOrUnsubscribe"; + promptFlags += Ci.nsIPromptService.BUTTON_POS_2 * Ci.nsIPromptService.BUTTON_TITLE_IS_STRING; + b0text = cal.l10n.getCalString("removeCalendarButtonUnsubscribe"); + b2text = cal.l10n.getCalString("removeCalendarButtonDelete"); + } else if (modes.has("unsubscribe")) { + textKey = "removeCalendarMessageUnsubscribe"; + removeFlags |= Ci.calICalendarManager.REMOVE_NO_DELETE; + b0text = cal.l10n.getCalString("removeCalendarButtonUnsubscribe"); + } else { + return; + } + + let text = cal.l10n.getCalString(textKey, [aCalendar.name]); + let res = Services.prompt.confirmEx( + window, + title, + text, + promptFlags, + b0text, + null, + b2text, + null, + {} + ); + + if (res != 1) { + // Not canceled + if (textKey == "removeCalendarMessageDeleteOrUnsubscribe" && res == 0) { + // Both unsubscribing and deleting is possible, but unsubscribing was + // requested. Make sure no delete is executed. + removeFlags |= Ci.calICalendarManager.REMOVE_NO_DELETE; + } + + cal.manager.removeCalendar(aCalendar, removeFlags); + } +} + +/** + * Call to refresh the status image of a calendar item when the + * calendar-readfailed or calendar-readonly attributes are added or removed. + * + * @param {MozRichlistitem} item - The calendar item to update. + */ +function updateCalendarStatusIndicators(item) { + let calendarName = item.querySelector(".calendar-name").textContent; + let image = item.querySelector("img.calendar-readstatus"); + if (item.hasAttribute("calendar-readfailed")) { + image.setAttribute("src", "chrome://messenger/skin/icons/new/compact/warning.svg"); + let tooltip = cal.l10n.getCalString("tooltipCalendarDisabled", [calendarName]); + image.setAttribute("title", tooltip); + } else if (item.hasAttribute("calendar-readonly")) { + image.setAttribute("src", "chrome://messenger/skin/icons/new/compact/lock.svg"); + let tooltip = cal.l10n.getCalString("tooltipCalendarReadOnly", [calendarName]); + image.setAttribute("title", tooltip); + } else { + image.removeAttribute("src"); + image.removeAttribute("title"); + } +} + +/** + * Called to initialize the calendar manager for a window. + */ +async function loadCalendarManager() { + let calendarList = document.getElementById("calendar-list"); + + // Set up the composite calendar in the calendar list widget. + let compositeCalendar = cal.view.getCompositeCalendar(window); + + // Initialize our composite observer + compositeCalendar.addObserver(compositeObserver); + + // Create the home calendar if no calendar exists. + let calendars = cal.manager.getCalendars(); + if (calendars.length) { + // migration code to make sure calendars, which do not support caching have cache enabled + // required to further clean up on top of bug 1182264 + for (let calendar of calendars) { + if ( + calendar.getProperty("cache.supported") === false && + calendar.getProperty("cache.enabled") === true + ) { + calendar.deleteProperty("cache.enabled"); + } + } + } else { + initHomeCalendar(); + } + + for (let calendar of sortCalendarArray(cal.manager.getCalendars())) { + addCalendarItem(calendar); + } + + function addCalendarItem(calendar) { + let item = document + .getElementById("calendar-list-item") + .content.firstElementChild.cloneNode(true); + let forceDisabled = calendar.getProperty("force-disabled"); + item.id = `calendar-listitem-${calendar.id}`; + item.searchLabel = calendar.name; + item.setAttribute("aria-label", calendar.name); + item.setAttribute("calendar-id", calendar.id); + item.toggleAttribute("calendar-disabled", calendar.getProperty("disabled")); + item.toggleAttribute( + "calendar-readfailed", + !Components.isSuccessCode(calendar.getProperty("currentStatus")) || forceDisabled + ); + item.toggleAttribute("calendar-readonly", calendar.readOnly); + item.toggleAttribute("calendar-muted", calendar.getProperty("suppressAlarms")); + document.l10n.setAttributes( + item.querySelector(".calendar-mute-status"), + "calendar-no-reminders-tooltip", + { calendarName: calendar.name } + ); + document.l10n.setAttributes( + item.querySelector(".calendar-more-button"), + "calendar-list-item-context-button", + { calendarName: calendar.name } + ); + + let cssSafeId = cal.view.formatStringForCSSRule(calendar.id); + let colorMarker = item.querySelector(".calendar-color"); + if (calendar.getProperty("disabled")) { + colorMarker.style.backgroundColor = "transparent"; + colorMarker.style.border = `2px solid var(--calendar-${cssSafeId}-backcolor)`; + } else { + colorMarker.style.backgroundColor = `var(--calendar-${cssSafeId}-backcolor)`; + } + + let label = item.querySelector(".calendar-name"); + label.textContent = calendar.name; + + updateCalendarStatusIndicators(item); + + let enable = item.querySelector(".calendar-enable-button"); + document.l10n.setAttributes(enable, "calendar-enable-button"); + + enable.hidden = forceDisabled || !calendar.getProperty("disabled"); + + let displayedCheckbox = item.querySelector(".calendar-displayed"); + displayedCheckbox.checked = calendar.getProperty("calendar-main-in-composite"); + displayedCheckbox.hidden = calendar.getProperty("disabled"); + let stringName = cal.view.getCompositeCalendar(window).getCalendarById(calendar.id) + ? "hideCalendar" + : "showCalendar"; + displayedCheckbox.setAttribute("title", cal.l10n.getCalString(stringName, [calendar.name])); + + calendarList.appendChild(item); + if (calendar.getProperty("calendar-main-default")) { + // The list needs to handle the addition of the row before we can select it. + setTimeout(() => { + calendarList.selectedIndex = calendarList.rows.indexOf(item); + }); + } + } + + function saveSortOrder() { + let order = [...calendarList.children].map(i => i.getAttribute("calendar-id")); + Services.prefs.setStringPref("calendar.list.sortOrder", order.join(" ")); + try { + Services.prefs.savePrefFile(null); + } catch (ex) { + cal.ERROR(ex); + } + } + + calendarList.addEventListener("click", event => { + if (event.target.matches(".calendar-enable-button")) { + let calendar = cal.manager.getCalendarById( + event.target.closest("li").getAttribute("calendar-id") + ); + calendar.setProperty("disabled", false); + calendarList.focus(); + return; + } + + if (!event.target.matches(".calendar-displayed")) { + return; + } + + let item = event.target.closest("li"); + let calendarId = item.getAttribute("calendar-id"); + let calendar = cal.manager.getCalendarById(calendarId); + + if (event.target.checked) { + compositeCalendar.addCalendar(calendar); + } else { + compositeCalendar.removeCalendar(calendar); + } + + let stringName = event.target.checked ? "hideCalendar" : "showCalendar"; + event.target.setAttribute("title", cal.l10n.getCalString(stringName, [calendar.name])); + + calendarList.focus(); + }); + calendarList.addEventListener("dblclick", event => { + if ( + event.target.matches(".calendar-displayed") || + event.target.matches(".calendar-enable-button") + ) { + return; + } + + let item = event.target.closest("li"); + if (!item) { + // Click on an empty part of the richlistbox. + cal.window.openCalendarWizard(window); + return; + } + + let calendarId = item.getAttribute("calendar-id"); + let calendar = cal.manager.getCalendarById(calendarId); + cal.window.openCalendarProperties(window, { calendar }); + }); + calendarList.addEventListener("ordered", event => { + saveSortOrder(); + calendarList.selectedIndex = calendarList.rows.indexOf(event.detail); + }); + calendarList.addEventListener("keypress", event => { + let item = calendarList.rows[calendarList.selectedIndex]; + let calendarId = item.getAttribute("calendar-id"); + let calendar = cal.manager.getCalendarById(calendarId); + + switch (event.key) { + case "Delete": + promptDeleteCalendar(calendar); + break; + case " ": { + if (item.querySelector(".calendar-displayed").checked) { + compositeCalendar.removeCalendar(calendar); + } else { + compositeCalendar.addCalendar(calendar); + } + let stringName = item.querySelector(".calendar-displayed").checked + ? "hideCalendar" + : "showCalendar"; + item + .querySelector(".calendar-displayed") + .setAttribute("title", cal.l10n.getCalString(stringName, [calendar.name])); + break; + } + } + }); + calendarList.addEventListener("select", event => { + let item = calendarList.rows[calendarList.selectedIndex]; + let calendarId = item.getAttribute("calendar-id"); + let calendar = cal.manager.getCalendarById(calendarId); + + compositeCalendar.defaultCalendar = calendar; + }); + + calendarList._calendarObserver = { + QueryInterface: ChromeUtils.generateQI(["calIObserver"]), + + onStartBatch() {}, + onEndBatch() {}, + onLoad() {}, + onAddItem(item) {}, + onModifyItem(newItem, oldItem) {}, + onDeleteItem(deletedItem) {}, + onError(calendar, errNo, message) {}, + + onPropertyChanged(calendar, name, value, oldValue) { + let item = calendarList.getElementsByAttribute("calendar-id", calendar.id)[0]; + if (!item) { + return; + } + + switch (name) { + case "disabled": + item.toggleAttribute("calendar-disabled", value); + item.querySelector(".calendar-displayed").hidden = value; + // Update the "ENABLE" button. + let enableButton = item.querySelector(".calendar-enable-button"); + enableButton.hidden = !value; + // Update the color preview. + let cssSafeId = cal.view.formatStringForCSSRule(calendar.id); + let colorMarker = item.querySelector(".calendar-color"); + colorMarker.style.backgroundColor = value + ? "transparent" + : `var(--calendar-${cssSafeId}-backcolor)`; + colorMarker.style.border = value + ? `2px solid var(--calendar-${cssSafeId}-backcolor)` + : "none"; + break; + case "calendar-main-default": + if (value) { + calendarList.selectedIndex = calendarList.rows.indexOf(item); + } + break; + case "calendar-main-in-composite": + item.querySelector(".calendar-displayed").checked = value; + break; + case "name": + item.searchLabel = calendar.name; + item.querySelector(".calendar-name").textContent = value; + break; + case "currentStatus": + case "force-disabled": + item.toggleAttribute( + "calendar-readfailed", + name == "currentStatus" ? !Components.isSuccessCode(value) : value + ); + updateCalendarStatusIndicators(item); + break; + case "readOnly": + item.toggleAttribute("calendar-readonly", value); + updateCalendarStatusIndicators(item); + break; + case "suppressAlarms": + item.toggleAttribute("calendar-muted", value); + break; + } + }, + + onPropertyDeleting(calendar, name) { + // Since the old value is not used directly in onPropertyChanged, but + // should not be the same as the value, set it to a different value. + this.onPropertyChanged(calendar, name, null, null); + }, + }; + cal.manager.addCalendarObserver(calendarList._calendarObserver); + + calendarList._calendarManagerObserver = { + QueryInterface: ChromeUtils.generateQI(["calICalendarManagerObserver"]), + + onCalendarRegistered(calendar) { + addCalendarItem(calendar); + saveSortOrder(); + }, + onCalendarUnregistering(calendar) { + let item = calendarList.getElementsByAttribute("calendar-id", calendar.id)[0]; + item.remove(); + saveSortOrder(); + }, + onCalendarDeleting(calendar) {}, + }; + cal.manager.addObserver(calendarList._calendarManagerObserver); +} + +/** + * Creates the initial "Home" calendar if no calendar exists. + */ +function initHomeCalendar() { + let composite = cal.view.getCompositeCalendar(window); + let url = Services.io.newURI("moz-storage-calendar://"); + let homeCalendar = cal.manager.createCalendar("storage", url); + homeCalendar.name = cal.l10n.getCalString("homeCalendarName"); + homeCalendar.setProperty("disabled", true); + + cal.manager.registerCalendar(homeCalendar); + Services.prefs.setStringPref("calendar.list.sortOrder", homeCalendar.id); + composite.addCalendar(homeCalendar); + + // Wrapping this in a try/catch block, as if any of the migration code + // fails, the app may not load. + if (Services.prefs.getBoolPref("calendar.migrator.enabled", true)) { + try { + gDataMigrator.checkAndMigrate(); + } catch (e) { + console.error("Migrator error: " + e); + } + } + + return homeCalendar; +} + +/** + * Called to clean up the calendar manager for a window. + */ +function unloadCalendarManager() { + let compositeCalendar = cal.view.getCompositeCalendar(window); + compositeCalendar.setStatusObserver(null, null); + compositeCalendar.removeObserver(compositeObserver); + + let calendarList = document.getElementById("calendar-list"); + cal.manager.removeCalendarObserver(calendarList._calendarObserver); + cal.manager.removeObserver(calendarList._calendarManagerObserver); +} + +/** + * A handler called to set up the context menu on the calendar list. + * + * @param {Event} event - The click DOMEvent. + */ + +function calendarListSetupContextMenu(event) { + let calendar; + let composite = cal.view.getCompositeCalendar(window); + + if (event.target.matches(".calendar-displayed")) { + return; + } + + let item = event.target.closest("li"); + if (item) { + let calendarList = document.getElementById("calendar-list"); + calendarList.selectedIndex = calendarList.rows.indexOf(item); + let calendarId = item.getAttribute("calendar-id"); + calendar = cal.manager.getCalendarById(calendarId); + } + + document.getElementById("list-calendars-context-menu").contextCalendar = calendar; + + for (let elem of document.querySelectorAll("#list-calendars-context-menu .needs-calendar")) { + elem.hidden = !calendar; + } + if (calendar) { + let stringName = composite.getCalendarById(calendar.id) ? "hideCalendar" : "showCalendar"; + document.getElementById("list-calendars-context-togglevisible").label = cal.l10n.getCalString( + stringName, + [calendar.name] + ); + let accessKey = document + .getElementById("list-calendars-context-togglevisible") + .getAttribute(composite.getCalendarById(calendar.id) ? "accesskeyhide" : "accesskeyshow"); + document.getElementById("list-calendars-context-togglevisible").accessKey = accessKey; + document.getElementById("list-calendars-context-showonly").label = cal.l10n.getCalString( + "showOnlyCalendar", + [calendar.name] + ); + setupDeleteMenuitem("list-calendars-context-delete", calendar); + document.getElementById("list-calendar-context-reload").hidden = !calendar.canRefresh; + document.getElementById("list-calendars-context-reload-menuseparator").hidden = + !calendar.canRefresh; + } +} + +/** + * Trigger the opening of the calendar list item context menu. + * + * @param {Event} event - The click DOMEvent. + */ +function openCalendarListItemContext(event) { + calendarListSetupContextMenu(event); + let popUpCalListMenu = document.getElementById("list-calendars-context-menu"); + if (event.type == "contextmenu" && event.button == 2) { + // This is a right-click. Open where it happened. + popUpCalListMenu.openPopupAtScreen(event.screenX, event.screenY, true); + return; + } + popUpCalListMenu.openPopup(event.target, "after_start", 0, 0, true); +} + +/** + * Changes the "delete calendar" menuitem to have the right label based on the + * removeModes. The menuitem must have the attributes "labelremove", + * "labeldelete" and "labelunsubscribe". + * + * @param aDeleteId The id of the menuitem to delete the calendar + */ +function setupDeleteMenuitem(aDeleteId, aCalendar) { + let calendar = aCalendar === undefined ? getSelectedCalendar() : aCalendar; + let modes = new Set( + calendar ? calendar.getProperty("capabilities.removeModes") || ["unsubscribe"] : [] + ); + + let type = "remove"; + if (modes.has("delete") && !modes.has("unsubscribe")) { + type = "delete"; + } else if (modes.has("unsubscribe") && !modes.has("delete")) { + type = "unsubscribe"; + } + + let deleteItem = document.getElementById(aDeleteId); + // Dynamically set labelremove, labeldelete, labelunsubscribe + deleteItem.label = deleteItem.getAttribute("label" + type); + // Dynamically set accesskeyremove, accesskeydelete, accesskeyunsubscribe + deleteItem.accessKey = deleteItem.getAttribute("accesskey" + type); +} + +/** + * Makes sure the passed calendar is visible to the user + * + * @param aCalendar The calendar to make visible. + */ +function ensureCalendarVisible(aCalendar) { + // We use the main window's calendar list to ensure that the calendar is visible. + // If the main window has been closed this function may still be called, + // like when an event/task window is still open and the user clicks 'save', + // thus we have the extra checks. + let calendarList = document.getElementById("calendar-list"); + if (calendarList) { + let compositeCalendar = cal.view.getCompositeCalendar(window); + compositeCalendar.addCalendar(aCalendar); + } +} + +/** + * Hides the specified calendar if it is visible, or shows it if it is hidden. + * + * @param aCalendar The calendar to show or hide + */ +function toggleCalendarVisible(aCalendar) { + let composite = cal.view.getCompositeCalendar(window); + if (composite.getCalendarById(aCalendar.id)) { + composite.removeCalendar(aCalendar); + } else { + composite.addCalendar(aCalendar); + } +} + +/** + * Shows all hidden calendars. + */ +function showAllCalendars() { + let composite = cal.view.getCompositeCalendar(window); + let cals = cal.manager.getCalendars(); + + composite.startBatch(); + for (let calendar of cals) { + if (!composite.getCalendarById(calendar.id)) { + composite.addCalendar(calendar); + } + } + composite.endBatch(); +} + +/** + * Shows only the specified calendar, and hides all others. + * + * @param aCalendar The calendar to show as the only visible calendar + */ +function showOnlyCalendar(aCalendar) { + let composite = cal.view.getCompositeCalendar(window); + let cals = composite.getCalendars() || []; + + composite.startBatch(); + for (let calendar of cals) { + if (calendar.id != aCalendar.id) { + composite.removeCalendar(calendar); + } + } + composite.addCalendar(aCalendar); + composite.endBatch(); +} + +var compositeObserver = { + QueryInterface: ChromeUtils.generateQI(["calIObserver", "calICompositeObserver"]), + + onStartBatch() {}, + onEndBatch() {}, + + onLoad() { + calendarUpdateNewItemsCommand(); + document.commandDispatcher.updateCommands("calendar_commands"); + }, + + onAddItem() {}, + onModifyItem() {}, + onDeleteItem() {}, + onError() {}, + + onPropertyChanged(calendar, name, value, oldValue) { + if (name == "disabled" || name == "readOnly") { + // Update commands when a calendar has been enabled or disabled. + calendarUpdateNewItemsCommand(); + document.commandDispatcher.updateCommands("calendar_commands"); + } + }, + + onPropertyDeleting() {}, + + onCalendarAdded(aCalendar) { + // Update the calendar commands for number of remote calendars and for + // more than one calendar. + calendarUpdateNewItemsCommand(); + document.commandDispatcher.updateCommands("calendar_commands"); + }, + + onCalendarRemoved(aCalendar) { + // Update commands to disallow deleting the last calendar and only + // allowing reload remote calendars when there are remote calendars. + calendarUpdateNewItemsCommand(); + document.commandDispatcher.updateCommands("calendar_commands"); + }, + + onDefaultCalendarChanged(aNewCalendar) { + // A new default calendar may mean that the new calendar has different + // ACLs. Make sure the commands are updated. + calendarUpdateNewItemsCommand(); + document.commandDispatcher.updateCommands("calendar_commands"); + }, +}; + +/** + * Shows the filepicker and creates a new calendar with a local file using the ICS + * provider. + */ +function openLocalCalendar() { + let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + picker.init(window, cal.l10n.getCalString("Open"), Ci.nsIFilePicker.modeOpen); + let wildmat = "*.ics"; + let description = cal.l10n.getCalString("filterIcs", [wildmat]); + picker.appendFilter(description, wildmat); + picker.appendFilters(Ci.nsIFilePicker.filterAll); + + picker.open(rv => { + if (rv != Ci.nsIFilePicker.returnOK || !picker.file) { + return; + } + + let calendars = cal.manager.getCalendars(); + let calendar = calendars.find(x => x.uri.equals(picker.fileURL)); + if (!calendar) { + calendar = cal.manager.createCalendar("ics", picker.fileURL); + + // Strip ".ics" from filename for use as calendar name. + let prettyName = picker.fileURL.spec.match(/([^/:]+)\.ics$/); + if (prettyName) { + calendar.name = decodeURIComponent(prettyName[1]); + } else { + calendar.name = cal.l10n.getCalString("untitledCalendarName"); + } + + cal.manager.registerCalendar(calendar); + } + + let calendarList = document.getElementById("calendar-list"); + for (let index = 0; index < calendarList.rowCount; index++) { + if (calendarList.rows[index].getAttribute("calendar-id") == calendar.id) { + calendarList.selectedIndex = index; + break; + } + } + }); +} + +/** + * Calendar Offline Manager + */ +var calendarOfflineManager = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + init() { + if (this.initialized) { + throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED); + } + Services.obs.addObserver(this, "network:offline-status-changed"); + + this.updateOfflineUI(!this.isOnline()); + this.initialized = true; + }, + + uninit() { + if (!this.initialized) { + throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED); + } + Services.obs.removeObserver(this, "network:offline-status-changed"); + this.initialized = false; + }, + + isOnline() { + return !Services.io.offline; + }, + + updateOfflineUI(aIsOffline) { + // Refresh the current view + currentView().goToDay(currentView().selectedDay); + + // Set up disabled locks for offline + document.commandDispatcher.updateCommands("calendar_commands"); + }, + + observe(aSubject, aTopic, aState) { + if (aTopic == "network:offline-status-changed") { + this.updateOfflineUI(aState == "offline"); + } + }, +}; diff --git a/comm/calendar/base/content/calendar-menu-events-tasks.inc.xhtml b/comm/calendar/base/content/calendar-menu-events-tasks.inc.xhtml new file mode 100644 index 0000000000..6967fad022 --- /dev/null +++ b/comm/calendar/base/content/calendar-menu-events-tasks.inc.xhtml @@ -0,0 +1,105 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/calendar/base/content/calendar-menus.js b/comm/calendar/base/content/calendar-menus.js new file mode 100644 index 0000000000..7dd71c3649 --- /dev/null +++ b/comm/calendar/base/content/calendar-menus.js @@ -0,0 +1,176 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from item-editing/calendar-item-panel.js */ + +// Importing from calendar-task-tree-utils.js puts ESLint in a fatal loop. +/* globals getSelectedTasks, MozElements, MozXULElement, + setAttributeOnChildrenOrTheirCommands */ + +"use strict"; + +// Wrap in a block and use const to define functions to prevent leaking to window scope. +{ + /** + * Get a property value for a group of tasks. If all the tasks have the same property value + * then return that value, otherwise return null. + * + * @param {string} propertyKey - The property key. + * @param {object[]} tasks - The tasks. + * @returns {string|null} The property value or null. + */ + const getPropertyValue = (propertyKey, tasks) => { + let propertyValue = null; + const tasksSelected = tasks != null && tasks.length > 0; + if (tasksSelected && tasks.every(task => task[propertyKey] == tasks[0][propertyKey])) { + propertyValue = tasks[0][propertyKey]; + } + return propertyValue; + }; + + /** + * Updates the 'checked' state of menu items so they reflect the state of the relevant task(s), + * for example, tasks currently selected in the task list, or a task being edited in the + * current tab. It operates on commands that are named using the following pattern: + * + * 'calendar_' + propertyKey + ' + '-' + propertyValue + '_command' + * + * When the propertyValue part of a command's name matches the propertyValue of the tasks, + * set the command to 'checked=true', as long as the tasks all have the same propertyValue. + * + * @param parent {Element} - Parent element that contains the menu items as direct children. + * @param propertyKey {string} - The property key, for example "priority" or "percentComplete". + */ + const updateMenuItemsState = (parent, propertyKey) => { + setAttributeOnChildrenOrTheirCommands("checked", false, parent); + + const inSingleTaskTab = + gTabmail && gTabmail.currentTabInfo && gTabmail.currentTabInfo.mode.type == "calendarTask"; + + const propertyValue = inSingleTaskTab + ? gConfig[propertyKey] + : getPropertyValue(propertyKey, getSelectedTasks()); + + if (propertyValue || propertyValue === 0) { + const commandName = "calendar_" + propertyKey + "-" + propertyValue + "_command"; + const command = document.getElementById(commandName); + if (command) { + command.setAttribute("checked", "true"); + } + } + }; + + /** + * A menupopup for changing the "progress" (percent complete) status for a task or tasks. It + * indicates the current status by displaying a checkmark next to the menu item for that status. + * + * @augments MozElements.MozMenuPopup + */ + class CalendarTaskProgressMenupopup extends MozElements.MozMenuPopup { + connectedCallback() { + if (this.delayConnectedCallback() || this.hasConnected) { + return; + } + // this.hasConnected is set to true in super.connectedCallback + super.connectedCallback(); + + this.appendChild( + MozXULElement.parseXULToFragment( + ` + + + + + + `, + ["chrome://calendar/locale/calendar.dtd"] + ) + ); + + this.addEventListener( + "popupshowing", + updateMenuItemsState.bind(null, this, "percentComplete"), + true + ); + } + } + + customElements.define("calendar-task-progress-menupopup", CalendarTaskProgressMenupopup, { + extends: "menupopup", + }); + + /** + * A menupopup for changing the "priority" status for a task or tasks. It indicates the current + * status by displaying a checkmark next to the menu item for that status. + * + * @augments MozElements.MozMenuPopup + */ + class CalendarTaskPriorityMenupopup extends MozElements.MozMenuPopup { + connectedCallback() { + if (this.delayConnectedCallback() || this.hasConnected) { + return; + } + // this.hasConnected is set to true in super.connectedCallback + super.connectedCallback(); + + this.appendChild( + MozXULElement.parseXULToFragment( + ` + + + + + `, + ["chrome://calendar/locale/calendar.dtd"] + ) + ); + + this.addEventListener( + "popupshowing", + updateMenuItemsState.bind(null, this, "priority"), + true + ); + } + } + + customElements.define("calendar-task-priority-menupopup", CalendarTaskPriorityMenupopup, { + extends: "menupopup", + }); +} diff --git a/comm/calendar/base/content/calendar-migration.js b/comm/calendar/base/content/calendar-migration.js new file mode 100644 index 0000000000..e5a5a1add2 --- /dev/null +++ b/comm/calendar/base/content/calendar-migration.js @@ -0,0 +1,323 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals putItemsIntoCal*/ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); + +/** + * A data migrator prototype, holding the information for migration + * + * @class + * @param aTitle The title of the migrator + * @param aMigrateFunction The function to call when migrating + * @param aArguments The arguments to pass in. + */ +function dataMigrator(aTitle, aMigrateFunction, aArguments) { + this.title = aTitle; + this.migrate = aMigrateFunction; + this.args = aArguments || []; +} + +var gDataMigrator = { + /** + * Call to do a general data migration (for a clean profile) Will run + * through all of the known migrator-checkers. These checkers will return + * an array of valid dataMigrator objects, for each kind of data they find. + * If there is at least one valid migrator, we'll pop open the migration + * wizard, otherwise, we'll return silently. + */ + checkAndMigrate() { + let DMs = []; + let migrators = [this.checkEvolution, this.checkWindowsMail, this.checkIcal]; + for (let migrator of migrators) { + let migs = migrator.call(this); + for (let mig of migs) { + DMs.push(mig); + } + } + + if (DMs.length == 0) { + // No migration available + return; + } + + let url = "chrome://calendar/content/calendar-migration-dialog.xhtml"; + if (AppConstants.platform == "macosx") { + let win = Services.wm.getMostRecentWindow("Calendar:MigrationWizard"); + if (win) { + win.focus(); + } else { + openDialog(url, "migration", "centerscreen,chrome,resizable=no,width=500,height=400", DMs); + } + } else { + openDialog( + url, + "migration", + "modal,centerscreen,chrome,resizable=no,width=500,height=400", + DMs + ); + } + }, + + /** + * Checks to see if Apple's iCal is installed and offers to migrate any data + * the user has created in it. + */ + checkIcal() { + function icalMigrate(aDataDir, aCallback) { + aDataDir.append("Sources"); + + let i = 1; + for (let dataDir of aDataDir.directoryEntries) { + let dataStore = dataDir.clone(); + dataStore.append("corestorage.ics"); + if (!dataStore.exists()) { + continue; + } + + let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + + fileStream.init(dataStore, 0x01, parseInt("0444", 8), {}); + let convIStream = Cc["@mozilla.org/intl/converter-input-stream;1"].getService( + Ci.nsIConverterInputStream + ); + convIStream.init(fileStream, "UTF-8", 0, 0x0000); + let tmpStr = {}; + let str = ""; + while (convIStream.readString(-1, tmpStr)) { + str += tmpStr.value; + } + + // Strip out the timezone definitions, since it makes the file + // invalid otherwise + let index = str.indexOf(";TZID="); + while (index != -1) { + let endIndex = str.indexOf(":", index); + let otherEnd = str.indexOf(";", index + 2); + if (otherEnd < endIndex) { + endIndex = otherEnd; + } + let sub = str.substring(index, endIndex); + str = str.split(sub).join(""); + index = str.indexOf(";TZID="); + } + let tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile); + tempFile.append("icalTemp.ics"); + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8)); + + let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + stream.init(tempFile, 0x2a, parseInt("0600", 8), 0); + let convOStream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance( + Ci.nsIConverterOutputStream + ); + convOStream.init(stream, "UTF-8"); + convOStream.writeString(str); + + let calendar = gDataMigrator.importICSToStorage(tempFile); + calendar.name = "iCalendar" + i; + i++; + cal.manager.registerCalendar(calendar); + cal.view.getCompositeCalendar(window).addCalendar(calendar); + } + console.debug("icalMig making callback"); + aCallback(); + } + + console.debug("Checking for ical data"); + let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + let icalSpec = profileDir.path; + let diverge = icalSpec.indexOf("Thunderbird"); + if (diverge == -1) { + return []; + } + icalSpec = icalSpec.substr(0, diverge); + let icalFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + icalFile.initWithPath(icalSpec); + icalFile.append("Application Support"); + + icalFile.append("iCal"); + if (icalFile.exists()) { + return [new dataMigrator("Apple iCal", icalMigrate, [icalFile])]; + } + + return []; + }, + + /** + * Checks to see if Evolution is installed and offers to migrate any data + * stored there. + */ + checkEvolution() { + function evoMigrate(aDataDir, aCallback) { + let i = 1; + let evoDataMigrate = function (dataStore) { + console.debug("Migrating evolution data file in " + dataStore.path); + if (dataStore.exists()) { + let calendar = gDataMigrator.importICSToStorage(dataStore); + calendar.name = "Evolution " + i++; + cal.manager.registerCalendar(calendar); + cal.view.getCompositeCalendar(window).addCalendar(calendar); + } + return dataStore.exists(); + }; + + for (let dataDir of aDataDir.directoryEntries) { + let dataStore = dataDir.clone(); + dataStore.append("calendar.ics"); + evoDataMigrate(dataStore); + } + + aCallback(); + } + + let evoDir = Services.dirsvc.get("Home", Ci.nsIFile); + evoDir.append(".evolution"); + evoDir.append("calendar"); + evoDir.append("local"); + return evoDir.exists() ? [new dataMigrator("Evolution", evoMigrate, [evoDir])] : []; + }, + + checkWindowsMail() { + function doMigrate(aCalendarNodes, aMailDir, aCallback) { + for (let node of aCalendarNodes) { + let name = node.getElementsByTagName("Name")[0].textContent; + let color = node.getElementsByTagName("Color")[0].textContent; + let enabled = node.getElementsByTagName("Enabled")[0].textContent == "True"; + + // The name is quoted, and the color also contains an alpha + // value. Lets just ignore the alpha value and take the + // color part. + name = name.replace(/(^'|'$)/g, ""); + color = color.replace(/0x[0-9a-fA-F]{2}([0-9a-fA-F]{4})/, "#$1"); + + let calfile = aMailDir.clone(); + calfile.append(name + ".ics"); + + if (calfile.exists()) { + let storage = gDataMigrator.importICSToStorage(calfile); + storage.name = name; + + if (color) { + storage.setProperty("color", color); + } + cal.manager.registerCalendar(storage); + + if (enabled) { + cal.view.getCompositeCalendar(window).addCalendar(storage); + } + } + } + aCallback(); + } + + if (!Services.dirsvc.has("LocalAppData")) { + // We are probably not on windows + return []; + } + + let maildir = Services.dirsvc.get("LocalAppData", Ci.nsIFile); + + maildir.append("Microsoft"); + maildir.append("Windows Calendar"); + maildir.append("Calendars"); + + let settingsxml = maildir.clone(); + settingsxml.append("Settings.xml"); + + let migrators = []; + if (settingsxml.exists()) { + let settingsXmlUri = Services.io.newFileURI(settingsxml); + + let req = new XMLHttpRequest(); + req.open("GET", settingsXmlUri.spec, false); + req.send(null); + if (req.status == 0) { + // The file was found, it seems we are on windows vista. + let doc = req.responseXML; + + // Get all calendar property tags and return the migrator. + let calendars = doc.getElementsByTagName("VCalendar"); + if (calendars.length > 0) { + migrators = [ + new dataMigrator("Windows Calendar", doMigrate.bind(null, calendars, maildir)), + ]; + } + } + } + return migrators; + }, + + /** + * Creates and registers a storage calendar and imports the given ics file into it. + * + * @param icsFile The nsI(Local)File to import. + */ + importICSToStorage(icsFile) { + const uri = "moz-storage-calendar://"; + let calendar = cal.manager.createCalendar("storage", Services.io.newURI(uri)); + let icsImporter = Cc["@mozilla.org/calendar/import;1?type=ics"].getService(Ci.calIImporter); + + let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + let items = []; + + calendar.id = cal.getUUID(); + + try { + const MODE_RDONLY = 0x01; + inputStream.init(icsFile, MODE_RDONLY, parseInt("0444", 8), {}); + items = icsImporter.importFromStream(inputStream); + } catch (ex) { + switch (ex.result) { + case Ci.calIErrors.INVALID_TIMEZONE: + cal.showError(cal.l10n.getCalString("timezoneError", [icsFile.path]), window); + break; + default: + cal.showError(cal.l10n.getCalString("unableToRead") + icsFile.path + "\n" + ex, window); + } + } finally { + inputStream.close(); + } + + // Defined in import-export.js + putItemsIntoCal(calendar, items, { + duplicateCount: 0, + failedCount: 0, + lastError: null, + + onDuplicate(item, error) { + this.duplicateCount++; + }, + onError(item, error) { + this.failedCount++; + this.lastError = error; + }, + onEnd() { + if (this.failedCount) { + cal.showError( + cal.l10n.getCalString("importItemsFailed", [ + this.failedCount, + this.lastError.toString(), + ]), + window + ); + } else if (this.duplicateCount) { + cal.showError( + cal.l10n.getCalString("duplicateError", [this.duplicateCount, icsFile.path]), + window + ); + } + }, + }); + + return calendar; + }, +}; diff --git a/comm/calendar/base/content/calendar-modes.js b/comm/calendar/base/content/calendar-modes.js new file mode 100644 index 0000000000..280a1c7a54 --- /dev/null +++ b/comm/calendar/base/content/calendar-modes.js @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals TodayPane, switchToView, gLastShownCalendarView, ensureUnifinderLoaded */ + +/* exported calSwitchToCalendarMode, calSwitchToMode, calSwitchToTaskMode, + * changeMode + */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +/** + * The current mode defining the current mode we're in. Allowed values are: + * - 'mail' + * - 'calendar' + * - 'task' + * - 'chat' + * - 'calendarEvent' + * - 'calendarTask' + * - 'special' - For special tabs like preferences, add-ons manager, about:xyz, etc. + * + * @global + */ +var gCurrentMode = "mail"; + +/** + * Changes the mode (gCurrentMode) and adapts the UI to the new mode. + * + * @param {string} [mode="mail"] - the new mode: 'mail', 'calendar', 'task', etc. + */ +function changeMode(mode = "mail") { + gCurrentMode = mode; // eslint-disable-line no-global-assign + + document + .querySelectorAll( + `menuitem[command="switch2calendar"],menuitem[command="switch2task"], + toolbarbutton[command="switch2calendar"],toolbarbutton[command="switch2task"]` + ) + .forEach(elem => { + elem.setAttribute("checked", elem.getAttribute("value") == gCurrentMode); + }); + + document.querySelectorAll("calendar-modebox,calendar-modevbox").forEach(elem => { + elem.setAttribute("current", gCurrentMode); + }); + + TodayPane.onModeModified(); +} + +/** + * For switching to modes like "mail", "chat", "calendarEvent", "calendarTask", or "special". + * (For "calendar" and "task" modes use calSwitchToCalendarMode and calSwitchToTaskMode.) + * + * @param {string} mode - The mode to switch to. + */ +function calSwitchToMode(mode) { + if (!["mail", "chat", "calendarEvent", "calendarTask", "special"].includes(mode)) { + cal.WARN("Attempted to switch to unknown mode: " + mode); + return; + } + if (gCurrentMode != mode) { + const previousMode = gCurrentMode; + changeMode(mode); + + if (previousMode == "calendar" || previousMode == "task") { + document.commandDispatcher.updateCommands("calendar_commands"); + } + window.setCursor("auto"); + } +} + +/** + * Switches to the calendar mode. + */ +function calSwitchToCalendarMode() { + if (gCurrentMode != "calendar") { + changeMode("calendar"); + + // display the calendar panel on the display deck + document.getElementById("calendar-view-box").collapsed = false; + document.getElementById("calendar-task-box").collapsed = true; + document.getElementById("sidePanelNewEvent").hidden = false; + document.getElementById("sidePanelNewTask").hidden = true; + + // show the last displayed type of calendar view + switchToView(gLastShownCalendarView.get()); + document.getElementById("calMinimonth").setAttribute("freebusy", "true"); + + document.commandDispatcher.updateCommands("calendar_commands"); + window.setCursor("auto"); + + // make sure the view is sized correctly + window.dispatchEvent(new CustomEvent("viewresize")); + + // Load the unifinder if it isn't already loaded. + ensureUnifinderLoaded(); + } +} + +/** + * Switches to the task mode. + */ +function calSwitchToTaskMode() { + if (gCurrentMode != "task") { + changeMode("task"); + + // display the task panel on the display deck + document.getElementById("calendar-view-box").collapsed = true; + document.getElementById("calendar-task-box").collapsed = false; + document.getElementById("sidePanelNewEvent").hidden = true; + document.getElementById("sidePanelNewTask").hidden = false; + + document.getElementById("calMinimonth").setAttribute("freebusy", "true"); + + let tree = document.getElementById("calendar-task-tree"); + if (!tree.hasBeenVisible) { + tree.hasBeenVisible = true; + tree.refresh(); + } + + document.commandDispatcher.updateCommands("calendar_commands"); + window.setCursor("auto"); + } +} diff --git a/comm/calendar/base/content/calendar-month-view.js b/comm/calendar/base/content/calendar-month-view.js new file mode 100644 index 0000000000..f4bd93b02d --- /dev/null +++ b/comm/calendar/base/content/calendar-month-view.js @@ -0,0 +1,1242 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* globals calendarNavigationBar, MozElements, MozXULElement */ + +/* import-globals-from calendar-ui-utils.js */ + +"use strict"; + +// Wrap in a block to prevent leaking to window scope. +{ + const { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + + /** + * Implements the Drag and Drop class for the Month Day Box view. + * + * @augments {MozElements.CalendarDnDContainer} + */ + class CalendarMonthDayBox extends MozElements.CalendarDnDContainer { + static get inheritedAttributes() { + return { + ".calendar-month-week-label": "relation,selected", + ".calendar-month-day-label": "relation,selected,text=value", + }; + } + + constructor() { + super(); + this.addEventListener("mousedown", this.onMouseDown); + this.addEventListener("dblclick", this.onDblClick); + this.addEventListener("click", this.onClick); + this.addEventListener("wheel", this.onWheel); + } + + connectedCallback() { + if (this.delayConnectedCallback() || this.hasConnected) { + return; + } + // this.hasConnected is set to true in super.connectedCallback. + super.connectedCallback(); + + this.mDate = null; + this.mItemHash = {}; + this.mShowMonthLabel = false; + + this.setAttribute("orient", "vertical"); + + let monthDayLabels = document.createElement("h2"); + monthDayLabels.classList.add("calendar-month-day-box-dates"); + + let weekLabel = document.createElement("span"); + weekLabel.setAttribute("data-label", "week"); + weekLabel.setAttribute("hidden", "true"); + weekLabel.style.pointerEvents = "none"; + weekLabel.classList.add("calendar-month-day-box-week-label", "calendar-month-week-label"); + + let dayLabel = document.createElement("span"); + dayLabel.setAttribute("data-label", "day"); + dayLabel.style.pointerEvents = "none"; + dayLabel.classList.add("calendar-month-day-box-date-label", "calendar-month-day-label"); + + monthDayLabels.appendChild(weekLabel); + monthDayLabels.appendChild(dayLabel); + + this.dayList = document.createElement("ol"); + this.dayList.classList.add("calendar-month-day-box-list"); + + this.appendChild(monthDayLabels); + this.appendChild(this.dayList); + + this.initializeAttributeInheritance(); + } + + get date() { + return this.mDate; + } + + set date(val) { + this.setDate(val); + } + + get selected() { + let sel = this.getAttribute("selected"); + if (sel && sel == "true") { + return true; + } + + return false; + } + + set selected(val) { + if (val) { + this.setAttribute("selected", "true"); + this.parentNode.setAttribute("selected", "true"); + } else { + this.removeAttribute("selected"); + this.parentNode.removeAttribute("selected"); + } + } + + get showMonthLabel() { + return this.mShowMonthLabel; + } + + set showMonthLabel(val) { + if (this.mShowMonthLabel == val) { + return; + } + this.mShowMonthLabel = val; + + if (!this.mDate) { + return; + } + if (val) { + this.setAttribute("value", cal.dtz.formatter.formatDateWithoutYear(this.mDate)); + } else { + this.setAttribute("value", this.mDate.day); + } + } + + clear() { + // Remove all the old events. + this.mItemHash = {}; + while (this.dayList.lastChild) { + this.dayList.lastChild.remove(); + } + } + + setDate(aDate) { + this.clear(); + + if (this.mDate && aDate && this.mDate.compare(aDate) == 0) { + return; + } + + this.mDate = aDate; + + if (!aDate) { + // Clearing out these attributes isn't strictly necessary but saves some confusion. + this.removeAttribute("year"); + this.removeAttribute("month"); + this.removeAttribute("week"); + this.removeAttribute("day"); + this.removeAttribute("value"); + return; + } + + // Set up DOM attributes for custom CSS coloring. + let weekTitle = cal.weekInfoService.getWeekTitle(aDate); + this.setAttribute("year", aDate.year); + this.setAttribute("month", aDate.month + 1); + this.setAttribute("week", weekTitle); + this.setAttribute("day", aDate.day); + + if (this.mShowMonthLabel) { + this.setAttribute("value", cal.dtz.formatter.formatDateWithoutYear(this.mDate)); + } else { + this.setAttribute("value", aDate.day); + } + } + + addItem(aItem) { + if (aItem.hashId in this.mItemHash) { + this.removeItem(aItem); + } + + let cssSafeId = cal.view.formatStringForCSSRule(aItem.calendar.id); + let box = document.createXULElement("calendar-month-day-box-item"); + let context = this.getAttribute("item-context") || this.getAttribute("context"); + box.setAttribute("context", context); + box.style.setProperty("--item-backcolor", `var(--calendar-${cssSafeId}-backcolor)`); + box.style.setProperty("--item-forecolor", `var(--calendar-${cssSafeId}-forecolor)`); + + let listItemWrapper = document.createElement("li"); + listItemWrapper.classList.add("calendar-month-day-box-list-item"); + listItemWrapper.appendChild(box); + cal.data.binaryInsertNode( + this.dayList, + listItemWrapper, + aItem, + cal.view.compareItems, + false, + // Access the calendar item from a list item wrapper. + wrapper => wrapper.firstChild.item + ); + + box.calendarView = this.calendarView; + box.item = aItem; + box.parentBox = this; + box.occurrence = aItem; + + this.mItemHash[aItem.hashId] = box; + return box; + } + + selectItem(aItem) { + if (aItem.hashId in this.mItemHash) { + this.mItemHash[aItem.hashId].selected = true; + } + } + + unselectItem(aItem) { + if (aItem.hashId in this.mItemHash) { + this.mItemHash[aItem.hashId].selected = false; + } + } + + removeItem(aItem) { + if (aItem.hashId in this.mItemHash) { + // Delete the list item wrapper. + let node = this.mItemHash[aItem.hashId].parentNode; + node.remove(); + delete this.mItemHash[aItem.hashId]; + } + } + + setDropShadow(on) { + let existing = this.dayList.querySelector(".dropshadow"); + if (on) { + if (!existing) { + // Insert an empty list item. + let dropshadow = document.createElement("li"); + dropshadow.classList.add("dropshadow", "calendar-month-day-box-list-item"); + this.dayList.insertBefore(dropshadow, this.dayList.firstElementChild); + } + } else if (existing) { + existing.remove(); + } + } + + onDropItem(aItem) { + // When item's timezone is different than the default one, the + // item might get moved on a day different than the drop day. + // Changing the drop day allows to compensate a possible difference. + + // Figure out if the timezones cause a days difference. + let start = ( + aItem[cal.dtz.startDateProp(aItem)] || aItem[cal.dtz.endDateProp(aItem)] + ).clone(); + let dayboxDate = this.mDate.clone(); + if (start.timezone != dayboxDate.timezone) { + let startInDefaultTz = start.clone().getInTimezone(dayboxDate.timezone); + start.isDate = true; + startInDefaultTz.isDate = true; + startInDefaultTz.timezone = start.timezone; + let dayDiff = start.subtractDate(startInDefaultTz); + // Change the day where to drop the item. + dayboxDate.addDuration(dayDiff); + } + + return cal.item.moveToDate(aItem, dayboxDate); + } + + onMouseDown(event) { + event.stopPropagation(); + if (this.mDate) { + this.calendarView.selectedDay = this.mDate; + } + } + + onDblClick(event) { + event.stopPropagation(); + this.calendarView.controller.createNewEvent(); + } + + onClick(event) { + if (event.button != 0) { + return; + } + + if (!(event.ctrlKey || event.metaKey)) { + this.calendarView.setSelectedItems([]); + } + } + + onWheel(event) { + if (cal.view.getParentNodeOrThisByAttribute(event.target, "data-label", "day") == null) { + if (this.dayList.scrollHeight > this.dayList.clientHeight) { + event.stopPropagation(); + } + } + } + } + + customElements.define("calendar-month-day-box", CalendarMonthDayBox); + + /** + * The MozCalendarMonthDayBoxItem widget is used as event item in the + * Multiweek and Month views of the calendar. It displays the event name, + * alarm icon and the category type color. + * + * @augments {MozElements.MozCalendarEditableItem} + */ + class MozCalendarMonthDayBoxItem extends MozElements.MozCalendarEditableItem { + static get inheritedAttributes() { + return { + ".alarm-icons-box": "flashing", + }; + } + connectedCallback() { + if (this.delayConnectedCallback() || this.hasChildNodes()) { + return; + } + // NOTE: This is the same structure as EditableItem, except this has a + // time label and we are missing the location-desc. + this.appendChild( + MozXULElement.parseXULToFragment(` + + + +