Bug 463402 - Remove E-Mail notification dialogs - Part 2: imip bar;r=philipp
authorMakeMyDay <makemyday@gmx-topmail.de>
Sat, 03 Mar 2018 16:49:41 +0100
changeset 31276 5ae749bd1ed8dbb33e1995c60fdc13152c066752
parent 31275 044240bd35e00b5c202b97d37ba532ef5f23d309
child 31277 f5138ae6a7280bdfab3eea5ee5db9fedd63504e9
push id383
push userclokep@gmail.com
push dateMon, 07 May 2018 21:52:48 +0000
reviewersphilipp
bugs463402
Bug 463402 - Remove E-Mail notification dialogs - Part 2: imip bar;r=philipp - providing the option to accept/decline an invitation from mail view with or without notifying the organizer - re-introcunding separate buttons for accept and tentative in invitation emails (having a pref to switch to 3 button mode, disabled by default)
calendar/base/modules/calItipUtils.jsm
calendar/itip/calItipEmailTransport.js
calendar/lightning/content/imip-bar-overlay.xul
calendar/lightning/content/imip-bar.js
calendar/lightning/content/lightning.js
calendar/lightning/themes/common/lightning.css
calendar/locales/en-US/chrome/lightning/lightning.dtd
calendar/locales/en-US/chrome/lightning/lightning.properties
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -263,39 +263,40 @@ cal.itip = {
      * }
      *
      * @see processItipItem   This takes the same parameters as its optionFunc.
      * @param itipItem        The itipItem to query.
      * @param rc              The result of retrieving the item
      * @param actionFunc      The action function.
      */
     getOptionsText: function(itipItem, rc, actionFunc, foundItems) {
-        function _gs(strName) {
-            return cal.calGetString("lightning", strName, null, "lightning");
+        function _gs(strName, aParam=null) {
+            return cal.calGetString("lightning", strName, aParam, "lightning");
         }
         let imipLabel = null;
         if (itipItem.receivedMethod) {
             imipLabel = cal.itip.getMethodText(itipItem.receivedMethod);
         }
-        let data = { label: imipLabel, buttons: [], hideMenuItems: [] };
+        let data = { label: imipLabel, showItems: [], hideItems: [] };
+        let separateButtons = Preferences.get("calendar.itip.separateInvitationButtons", false);
 
         let disallowedCounter = false;
         if (foundItems && foundItems.length) {
             let disallow = foundItems[0].getProperty("X-MICROSOFT-DISALLOW-COUNTER");
             disallowedCounter = disallow && disallow == "TRUE";
         }
         if (rc == Components.interfaces.calIErrors.CAL_IS_READONLY) {
             // No writable calendars, tell the user about it
             data.label = _gs("imipBarNotWritable");
         } else if (Components.isSuccessCode(rc) && !actionFunc) {
             // This case, they clicked on an old message that has already been
             // added/updated, we want to tell them that.
             data.label = _gs("imipBarAlreadyProcessedText");
             if (foundItems && foundItems.length) {
-                data.buttons.push("imipDetailsButton");
+                data.showItems.push("imipDetailsButton");
                 if (itipItem.receivedMethod == "COUNTER" && itipItem.sender) {
                     if (disallowedCounter) {
                         data.label = _gs("imipBarDisallowedCounterText");
                     } else {
                         let comparison;
                         for (let item of itipItem.getItemList({})) {
                             let attendees = cal.getAttendeesBySender(
                                     item.getAttendees({}),
@@ -334,65 +335,104 @@ cal.itip = {
         } else if (Components.isSuccessCode(rc)) {
             cal.LOG("iTIP options on: " + actionFunc.method);
             switch (actionFunc.method) {
                 case "PUBLISH:UPDATE":
                 case "REQUEST:UPDATE-MINOR":
                     data.label = _gs("imipBarUpdateText");
                     // falls through
                 case "REPLY":
-                    data.buttons.push("imipUpdateButton");
+                    data.showItems.push("imipUpdateButton");
                     break;
                 case "PUBLISH":
-                    data.buttons.push("imipAddButton");
+                    data.showItems.push("imipAddButton");
                     break;
                 case "REQUEST:UPDATE":
                 case "REQUEST:NEEDS-ACTION":
                 case "REQUEST": {
-                    if (actionFunc.method == "REQUEST:UPDATE") {
-                        data.label = _gs("imipBarUpdateText");
-                    } else if (actionFunc.method == "REQUEST:NEEDS-ACTION") {
-                        data.label = _gs("imipBarProcessedNeedsAction");
-                    }
-
                     let isRecurringMaster = false;
                     for (let item of itipItem.getItemList({})) {
                         if (item.recurrenceInfo) {
                             isRecurringMaster = true;
                         }
                     }
+
+                    if (actionFunc.method == "REQUEST:UPDATE") {
+                        if (isRecurringMaster) {
+                            data.label = _gs("imipBarUpdateSeriesText");
+                        } else if (itipItem.getItemList({}).length > 1) {
+                            data.label = _gs("imipBarUpdateMultipleText");
+                        } else {
+                            data.label = _gs("imipBarUpdateText");
+                        }
+                    } else if (actionFunc.method == "REQUEST:NEEDS-ACTION") {
+                        if (isRecurringMaster) {
+                            data.label = _gs("imipBarProcessedSeriesNeedsAction");
+                        } else if (itipItem.getItemList({}).length > 1) {
+                            data.label = _gs("imipBarProcessedMultipleNeedsAction");
+                        } else {
+                            data.label = _gs("imipBarProcessedNeedsAction");
+                        }
+                    }
+
                     if (itipItem.getItemList({}).length > 1 || isRecurringMaster) {
-                        data.buttons.push("imipAcceptRecurrencesButton");
-                        data.buttons.push("imipDeclineRecurrencesButton");
+                        data.showItems.push("imipAcceptRecurrencesButton");
+                        if (separateButtons) {
+                            data.showItems.push("imipTentativeRecurrencesButton");
+                            data.hideItems.push("imipAcceptRecurrencesButton_AcceptLabel");
+                            data.hideItems.push("imipAcceptRecurrencesButton_TentativeLabel");
+                            data.hideItems.push("imipAcceptRecurrencesButton_Tentative");
+                            data.hideItems.push("imipAcceptRecurrencesButton_TentativeDontSend");
+                        } else {
+                            data.hideItems.push("imipTentativeRecurrencesButton");
+                            data.showItems.push("imipAcceptRecurrencesButton_AcceptLabel");
+                            data.showItems.push("imipAcceptRecurrencesButton_TentativeLabel");
+                            data.showItems.push("imipAcceptRecurrencesButton_Tentative");
+                            data.showItems.push("imipAcceptRecurrencesButton_TentativeDontSend");
+                        }
+                        data.showItems.push("imipDeclineRecurrencesButton");
                     } else {
-                        data.buttons.push("imipAcceptButton");
-                        data.buttons.push("imipDeclineButton");
+                        data.showItems.push("imipAcceptButton");
+                        if (separateButtons) {
+                            data.showItems.push("imipTentativeButton");
+                            data.hideItems.push("imipAcceptButton_AcceptLabel");
+                            data.hideItems.push("imipAcceptButton_TentativeLabel");
+                            data.hideItems.push("imipAcceptButton_Tentative");
+                            data.hideItems.push("imipAcceptButton_TentativeDontSend");
+                        } else {
+                            data.hideItems.push("imipTentativeButton");
+                            data.showItems.push("imipAcceptButton_AcceptLabel");
+                            data.showItems.push("imipAcceptButton_TentativeLabel");
+                            data.showItems.push("imipAcceptButton_Tentative");
+                            data.showItems.push("imipAcceptButton_TentativeDontSend");
+                        }
+                        data.showItems.push("imipDeclineButton");
                     }
-                    data.buttons.push("imipMoreButton");
-                    // Use data.hideMenuItems.push("idOfMenuItem") to hide specific menuitems
+                    data.showItems.push("imipMoreButton");
+                    // Use data.hideItems.push("idOfMenuItem") to hide specific menuitems
                     // from the dropdown menu of a button.  This might be useful to to remove
                     // a generally available option for a specific invitation, because the
                     // respective feature is not available for the calendar, the invitation
                     // is in or the feature is prohibited by the organizer
                     break;
                 }
                 case "CANCEL": {
-                    data.buttons.push("imipDeleteButton");
+                    data.showItems.push("imipDeleteButton");
                     break;
                 }
                 case "REFRESH": {
-                    data.buttons.push("imipReconfirmButton");
+                    data.showItems.push("imipReconfirmButton");
                     break;
                 }
                 case "COUNTER": {
                     if (disallowedCounter) {
                         data.label = _gs("imipBarDisallowedCounterText");
                     }
-                    data.buttons.push("imipDeclineCounterButton");
-                    data.buttons.push("imipRescheduleButton");
+                    data.showItems.push("imipDeclineCounterButton");
+                    data.showItems.push("imipRescheduleButton");
                     break;
                 }
                 default:
                     data.label = _gs("imipBarUnsupportedText");
                     break;
             }
         } else {
             data.label = _gs("imipBarUnsupportedText");
--- a/calendar/itip/calItipEmailTransport.js
+++ b/calendar/itip/calItipEmailTransport.js
@@ -41,17 +41,17 @@ calItipEmailTransport.prototype = {
         return this.mSenderAddress;
     },
     set senderAddress(aValue) {
         return (this.mSenderAddress = aValue);
     },
 
     sendItems: function(aCount, aRecipients, aItipItem) {
         if (this.mHasXpcomMail) {
-            cal.LOG("sendItems: Sending Email...");
+            cal.LOG("sendItems: Preparing to send an invitation email...");
             let items = this._prepareItems(aItipItem);
             if (items === false) {
                 return false;
             }
 
             return this._sendXpcomMail(aRecipients, items.subject, items.body, aItipItem);
         } else {
             // sending xpcom mail is not available if no identity has been set
@@ -236,41 +236,42 @@ calItipEmailTransport.prototype = {
             case Ci.calIItipItem.USER: {
                 cal.LOG("sendXpcomMail: Found USER autoResponse type.");
                 // We still need this as a last resort if a user just deletes or
                 //  drags an invitation related event
                 let parent = Services.wm.getMostRecentWindow(null);
                 if (parent.closed) {
                     parent = cal.window.getCalendarWindow();
                 }
-                let confirmed = Services.prompt.confirmEx(
+                let cancelled = Services.prompt.confirmEx(
                     parent,
                     cal.calGetString("lightning", "imipSendMail.title", null, "lightning"),
                     cal.calGetString("lightning", "imipSendMail.text", null, "lightning"),
                     Services.prompt.STD_YES_NO_BUTTONS,
                     null,
                     null,
                     null,
                     null,
                     {}
                 );
-                if (!confirmed) {
+                if (cancelled) {
+                    cal.LOG("sendXpcomMail: Sending of invitation email aborted by user!");
                     break;
                 } // else go on with auto sending for now
             }
             // falls through intended
             case Ci.calIItipItem.AUTO: {
                 // don't show log message in case of falling through
                 if (aItipItem.autoResponse == Ci.calIItipItem.AUTO) {
                     cal.LOG("sendXpcomMail: Found AUTO autoResponse type.");
                 }
                 let cbEmail = function(aVal, aInd, aArr) {
                     let email = cal.getAttendeeEmail(aVal, true);
                     if (!email.length) {
-                        cal.LOG("Invalid recipient for email transport: " + aVal.toString());
+                        cal.LOG("sendXpcomMail: Invalid recipient for email transport: " + aVal.toString());
                     }
                     return email;
                 };
                 let toMap = aToList.map(cbEmail).filter(value => value.length);
                 if (toMap.length < aToList.length) {
                     // at least one invalid recipient, so we skip sending for this message
                     return false;
                 }
--- a/calendar/lightning/content/imip-bar-overlay.xul
+++ b/calendar/lightning/content/imip-bar-overlay.xul
@@ -128,120 +128,170 @@
                              class="toolbarbutton-1 msgHeaderView-button imipReconfirmButton"
                              oncommand="ltnImipBar.executeAction()"
                              hidden="true"/>
 
               <!-- accept -->
               <toolbarbutton id="imipAcceptButton"
                              tooltiptext="&lightning.imipbar.btnAccept2.tooltiptext;"
                              label="&lightning.imipbar.btnAccept.label;"
-                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('ACCEPTED');"
+                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('ACCEPTED', 'AUTO');"
                              type="menu-button"
                              class="imip-button toolbarbutton-1 msgHeaderView-button imipAcceptButton"
                              hidden="true">
                 <menupopup id="imipAcceptDropdown">
+                  <label id="imipAcceptButton_AcceptLabel"
+                         class="imipAcceptLabel"
+                         tooltiptext="&lightning.imipbar.btnAccept2.tooltiptext;"
+                         value="&lightning.imipbar.btnAccept.label;"/>
                   <menuitem id="imipAcceptButton_Accept"
-                            tooltiptext="&lightning.imipbar.btnAccept2.tooltiptext;"
-                            label="&lightning.imipbar.btnAccept.label;"
-                            oncommand="ltnImipBar.executeAction('ACCEPTED');"/>
+                            tooltiptext="&lightning.imipbar.btnSend.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('ACCEPTED', 'AUTO');"/>
+                  <menuitem id="imipAcceptButton_AcceptDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSend.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('ACCEPTED', 'NONE');"/>
+                  <separatpor flex="1" class="groove"/>
+                  <label id="imipAcceptButton_TentativeLabel"
+                         class="imipAcceptLabel"
+                         tooltiptext="&lightning.imipbar.btnTentative2.tooltiptext;"
+                         value="&lightning.imipbar.btnTentative.label;"/>
                   <menuitem id="imipAcceptButton_Tentative"
-                            tooltiptext="&lightning.imipbar.btnTentative2.tooltiptext;"
-                            label="&lightning.imipbar.btnTentative.label;"
-                            oncommand="ltnImipBar.executeAction('TENTATIVE');"/>
+                            tooltiptext="&lightning.imipbar.btnSend.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'AUTO');"/>
+                  <menuitem id="imipAcceptButton_TentativeDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSend.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- accept recurrences -->
               <toolbarbutton id="imipAcceptRecurrencesButton"
                              tooltiptext="&lightning.imipbar.btnAcceptRecurrences2.tooltiptext;"
                              label="&lightning.imipbar.btnAcceptRecurrences.label;"
-                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('ACCEPTED');"
+                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('ACCEPTED', 'AUTO');"
                              type="menu-button"
                              class="imip-button toolbarbutton-1 msgHeaderView-button imipAcceptRecurrencesButton"
                              hidden="true">
                 <menupopup id="imipAcceptRecurrencesDropdown">
+                  <label id="imipAcceptRecurrencesButton_AcceptLabel"
+                         class="imipAcceptLabel"
+                         tooltiptext="&lightning.imipbar.btnAcceptRecurrences2.tooltiptext;"
+                         value="&lightning.imipbar.btnAcceptRecurrences.label;"/>
                   <menuitem id="imipAcceptRecurrencesButton_Accept"
-                            tooltiptext="&lightning.imipbar.btnAcceptRecurrences2.tooltiptext;"
-                            label="&lightning.imipbar.btnAcceptRecurrences.label;"
-                            oncommand="ltnImipBar.executeAction('ACCEPTED');"/>
+                            tooltiptext="&lightning.imipbar.btnSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('ACCEPTED', 'AUTO');"/>
+                  <menuitem id="imipAcceptRecurrencesButton_AcceptDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('ACCEPTED', 'NONE');"/>
+                  <separatpor flex="1" class="groove"/>
+                  <label id="imipAcceptRecurrencesButton_TentativeLabel"
+                         class="imipAcceptLabel"
+                         tooltiptext="&lightning.imipbar.btnTentativeRecurrences2.tooltiptext;"
+                         value="&lightning.imipbar.btnTentativeRecurrences.label;"/>
                   <menuitem id="imipAcceptRecurrencesButton_Tentative"
-                            tooltiptext="&lightning.imipbar.btnTentativeRecurrences2.tooltiptext;"
-                            label="&lightning.imipbar.btnTentativeRecurrences.label;"
-                            oncommand="ltnImipBar.executeAction('TENTATIVE');"/>
+                            tooltiptext="&lightning.imipbar.btnSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'AUTO');"/>
+                  <menuitem id="imipAcceptRecurrencesButton_TentativeDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- tentative; should only be used, if no imipMoreButton is used and
                  - imipDeclineButton/imipAcceptButton have no visible menuitems //-->
               <toolbarbutton id="imipTentativeButton"
                              label="&lightning.imipbar.btnTentative.label;"
                              tooltiptext="&lightning.imipbar.btnTentative2.tooltiptext;"
                              class="toolbarbutton-1 msgHeaderView-button imipTentativeButton"
-                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('TENTATIVE');"
+                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('TENTATIVE', 'AUTO');"
                              type="menu-button"
                              hidden="true">
                 <menupopup id="imipTentativeDropdown">
                   <menuitem id="imipTentativeButton_Tentative"
-                            tooltiptext="&lightning.imipbar.btnTentative2.tooltiptext;"
-                            label="&lightning.imipbar.btnTentative.label;"
-                            oncommand="ltnImipBar.executeAction('TENTATIVE');"/>
+                            tooltiptext="&lightning.imipbar.btnSend.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'AUTO');"/>
+                  <menuitem id="imipTentativeButton_TentativeDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSend.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- tentative recurrences; should only be used, if no imipMoreButton is used and
                  - imipDeclineRecurrencesButton/imipAcceptRecurrencesButton have no visible menuitems //-->
               <toolbarbutton id="imipTentativeRecurrencesButton"
                              label="&lightning.imipbar.btnTentativeRecurrences.label;"
                              tooltiptext="&lightning.imipbar.btnTentativeRecurrences2.tooltiptext;"
                              class="toolbarbutton-1 msgHeaderView-button imipTentativeRecurrencesButton"
-                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('TENTATIVE');"
+                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('TENTATIVE', 'AUTO');"
                              type="menu-button"
                              hidden="true">
                 <menupopup id="imipTentativeRecurrencesDropdown">
                   <menuitem id="imipTentativeRecurrencesButton_Tentative"
-                            tooltiptext="&lightning.imipbar.btnTentativeRecurrences2.tooltiptext;"
-                            label="&lightning.imipbar.btnTentativeRecurrences.label;"
-                            oncommand="ltnImipBar.executeAction('TENTATIVE');"/>
+                            tooltiptext="&lightning.imipbar.btnSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'AUTO');"/>
+                  <menuitem id="imipTentativeRecurrencesButton_TentativeDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('TENTATIVE', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- decline -->
               <toolbarbutton id="imipDeclineButton"
                              tooltiptext="&lightning.imipbar.btnDecline2.tooltiptext;"
                              label="&lightning.imipbar.btnDecline.label;"
-                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('DECLINED');"
+                             oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('DECLINED', 'AUTO');"
                              type="menu-button"
                              class="toolbarbutton-1 msgHeaderView-button imipDeclineButton"
                              hidden="true">
                 <menupopup id="imipDeclineDropdown">
                   <menuitem id="imipDeclineButton_Decline"
-                            tooltiptext="&lightning.imipbar.btnDecline2.tooltiptext;"
-                            label="&lightning.imipbar.btnDecline.label;"
-                            oncommand="ltnImipBar.executeAction('DECLINED');"/>
+                            tooltiptext="&lightning.imipbar.btnSend.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
+                            oncommand="ltnImipBar.executeAction('DECLINED', 'AUTO');"/>
+                  <menuitem id="imipDeclineButton_DeclineDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSend.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('DECLINED', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- decline recurrences -->
               <toolbarbutton id="imipDeclineRecurrencesButton"
                              tooltiptext="&lightning.imipbar.btnDeclineRecurrences2.tooltiptext;"
                              label="&lightning.imipbar.btnDeclineRecurrences.label;"
                              oncommand="if (event.target.id == this.id) ltnImipBar.executeAction('DECLINED');"
                              type="menu-button"
                              class="toolbarbutton-1 msgHeaderView-button imipDeclineRecurrencesButton"
                              hidden="true">
                 <menupopup id="imipDeclineRecurrencesDropdown">
                   <menuitem id="imipDeclineRecurrencesButton_DeclineAll"
-                            tooltiptext="&lightning.imipbar.btnDeclineRecurrences2.tooltiptext;"
-                            label="&lightning.imipbar.btnDeclineRecurrences.label;"
+                            tooltiptext="&lightning.imipbar.btnSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnSend.label;"
                             oncommand="ltnImipBar.executeAction('DECLINED');"/>
+                  <menuitem id="imipDeclineRecurrencesButton_DeclineAllDontSend"
+                            tooltiptext="&lightning.imipbar.btnDontSendSeries.tooltiptext;"
+                            label="&lightning.imipbar.btnDontSend.label;"
+                            oncommand="ltnImipBar.executeAction('DECLINED', 'NONE');"/>
                   <!-- add here more menuitem as needed -->
                 </menupopup>
               </toolbarbutton>
 
               <!-- more options -->
               <toolbarbutton id="imipMoreButton"
                              type="menu"
                              tooltiptext="&lightning.imipbar.btnMore.tooltiptext;"
--- a/calendar/lightning/content/imip-bar.js
+++ b/calendar/lightning/content/imip-bar.js
@@ -231,20 +231,25 @@ var ltnImipBar = {
                     hideMenuItems: []
                 };
             }
         }
 
         imipBar.setAttribute("label", data.label);
         // let's reset all buttons first
         ltnImipBar.resetButtons();
-        // menu items are visible by default, let's hide what's not available
-        data.hideMenuItems.forEach(aElementId => hideElement(document.getElementById(aElementId)));
-        // buttons are hidden by default, let's make required buttons visible
-        data.buttons.forEach(aElementId => showElement(document.getElementById(aElementId)));
+        // now we update the visible items - buttons are hidden by default
+        // apart from that, we need this to adapt the accept button depending on
+        // whether three or four button style is present
+        for (let item of data.hideItems) {
+            hideElement(document.getElementById(item));
+        }
+        for (let item of data.showItems) {
+            showElement(document.getElementById(item));
+        }
         // adjust button style if necessary
         ltnImipBar.conformButtonType();
         ltnImipBar.displayModifications();
     },
 
     /**
      * Displays changes in case of invitation updates in invitation overlay
      */
@@ -272,26 +277,46 @@ var ltnImipBar = {
                 // meanwhile accepted invitation, so we flip comparison order
                 msgOverlay = ltn.invitation.compareInvitationOverlay(msgOverlay, serializedOverlay,
                                                                      organizerId);
             }
         }
         msgWindow.displayHTMLInMessagePane("", msgOverlay, false);
     },
 
-    executeAction: function(partStat, extendResponse) {
-        function _execAction(aActionFunc, aItipItem, aWindow, aPartStat) {
+    /**
+     * Exceutes an action triggered by an imip bar button
+     *
+     * @param   {String}  aParticipantStatus  A partstat string as per RfC 5545
+     * @param   {String}  aResponse           Either 'AUTO', 'NONE' or 'USER',
+     *                                          see calItipItem interface
+     * @returns {Boolean}                     true, if the action succeeded
+     */
+    executeAction: function(aParticipantStatus, aResponse) {
+        /**
+         * Internal function to trigger an scheduling operation
+         *
+         * @param   {Function}     aActionFunc   The function to call to do the
+         *                                         scheduling operation
+         * @param   {calIItipItem} aItipItem     Scheduling item
+         * @param   {nsIWindow}    aWindow       The current window
+         * @param   {String}       aPartStat     partstat string as per RfC 5545
+         * @param   {Object}       aExtResponse  JS object containing at least
+         *                                         an responseMode property
+         * @returns {Boolean}                    true, if the action succeeded
+         */
+        function _execAction(aActionFunc, aItipItem, aWindow, aPartStat, aExtResponse) {
             if (cal.itip.promptCalendar(aActionFunc.method, aItipItem, aWindow)) {
                 let isDeclineCounter = aPartStat == "X-DECLINECOUNTER";
                 // filter out fake partstats
                 if (aPartStat.startsWith("X-")) {
-                    partStat = "";
+                    aParticipantStatus = "";
                 }
                 // hide the buttons now, to disable pressing them twice...
-                if (aPartStat == partStat) {
+                if (aPartStat == aParticipantStatus) {
                     ltnImipBar.resetButtons();
                 }
 
                 let opListener = {
                     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIOperationListener]),
                     onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
                         if (Components.isSuccessCode(aStatus) && isDeclineCounter) {
                             // TODO: move the DECLINECOUNTER stuff to actionFunc
@@ -342,36 +367,37 @@ var ltnImipBar = {
                             cal.showError(label);
                         }
                     },
                     onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
                     }
                 };
 
                 try {
-                    aActionFunc(opListener, partStat);
+                    aActionFunc(opListener, aParticipantStatus, aExtResponse);
                 } catch (exc) {
                     Components.utils.reportError(exc);
                 }
                 return true;
             }
             return false;
         }
 
         let imipBar = document.getElementById("imip-bar");
-        if (partStat == null) {
-            partStat = "";
+        if (aParticipantStatus == null) {
+            aParticipantStatus = "";
         }
-        if (partStat == "X-SHOWDETAILS" || partStat == "X-RESCHEDULE") {
+        if (aParticipantStatus == "X-SHOWDETAILS" ||
+            aParticipantStatus == "X-RESCHEDULE") {
             let counterProposal;
             let items = ltnImipBar.foundItems;
             if (items && items.length) {
                 let item = items[0].isMutable ? items[0] : items[0].clone();
 
-                if (partStat == "X-RESCHEDULE") {
+                if (aParticipantStatus == "X-RESCHEDULE") {
                     // TODO most of the following should be moved to the actionFunc defined in
                     // calItipUtils
                     let proposedItem = ltnImipBar.itipItem.getItemList({})[0];
                     let proposedRID = proposedItem.getProperty("RECURRENCE-ID");
                     if (proposedRID) {
                         // if this is a counterproposal for a specific occurrence, we use
                         // that to compare with
                         item = item.recurrenceInfo.getOccurrenceFor(proposedRID).clone();
@@ -408,63 +434,80 @@ var ltnImipBar = {
                             cal.LOG(parsedProposal.result.descr);
                         } else {
                             cal.LOG("Failed to identify the sending attendee of the counterproposal.");
                         }
 
                         return false;
                     }
                 }
-                // if this a rescheduling operation, we suppress the occurrence prompt here
-                modifyEventWithDialog(item, null, partStat != "X-RESCHEDULE", null, counterProposal);
+                // if this a rescheduling operation, we suppress the occurrence
+                // prompt here
+                modifyEventWithDialog(
+                    item,
+                    null,
+                    aParticipantStatus != "X-RESCHEDULE",
+                    null,
+                    counterProposal
+                );
             }
         } else {
-            if (extendResponse) {
+            if (aResponse) {
+                if (aResponse == "AUTO" || aResponse == "NONE" || aResponse == "USER") {
+                    response = { responseMode: Components.interfaces.calIItipItem[aResponse] };
+                }
                 // Open an extended response dialog to enable the user to add a comment, make a
                 // counterproposal, delegate the event or interact in another way.
                 // Instead of a dialog, this might be implemented as a separate container inside the
                 // imip-overlay as proposed in bug 458578
-                //
-                // If implemented as a dialog, the OL compatibility decision should be incorporated
-                // therein too and the itipItems's autoResponse set to auto subsequently
-                // to prevent a second popup during imip transport processing.
             }
             let delmgr = Components.classes["@mozilla.org/calendar/deleted-items-manager;1"]
                                    .getService(Components.interfaces.calIDeletedItems);
             let items = ltnImipBar.itipItem.getItemList({});
             if (items && items.length) {
                 let delTime = delmgr.getDeletedDate(items[0].id);
                 let dialogText = ltnGetString("lightning", "confirmProcessInvitation");
                 let dialogTitle = ltnGetString("lightning", "confirmProcessInvitationTitle");
                 if (delTime && !Services.prompt.confirm(window, dialogTitle, dialogText)) {
                     return false;
                 }
             }
 
-            if (partStat == "X-SAVECOPY") {
+            if (aParticipantStatus == "X-SAVECOPY") {
                 // we create and adopt copies of the respective events
                 let saveitems = ltnImipBar.itipItem.getItemList({}).map(cal.getPublishLikeItemCopy.bind(cal));
                 if (saveitems.length > 0) {
                     let methods = { receivedMethod: "PUBLISH", responseMethod: "PUBLISH" };
                     let newItipItem = cal.itip.getModifiedItipItem(ltnImipBar.itipItem,
                                                                    saveitems, methods);
                     // control to avoid processing _execAction on later user changes on the item
                     let isFirstProcessing = true;
                     // setup callback and trigger re-processing
                     let storeCopy = function(aItipItem, aRc, aActionFunc, aFoundItems) {
                         if (isFirstProcessing && aActionFunc && Components.isSuccessCode(aRc)) {
-                            _execAction(aActionFunc, aItipItem, window, partStat);
+                            _execAction(
+                                aActionFunc,
+                                aItipItem,
+                                window,
+                                aParticipantStatus
+                            );
                         }
                     };
                     cal.itip.processItipItem(newItipItem, storeCopy);
                     isFirstProcessing = false;
                 }
                 // we stop here to not process the original item
                 return false;
             }
-            return _execAction(ltnImipBar.actionFunc, ltnImipBar.itipItem, window, partStat);
+            return _execAction(
+                ltnImipBar.actionFunc,
+                ltnImipBar.itipItem,
+                window,
+                aParticipantStatus,
+                response
+            );
         }
         return false;
     }
 };
 
 addEventListener("messagepane-loaded", ltnImipBar.load, true);
 addEventListener("messagepane-unloaded", ltnImipBar.unload, true);
--- a/calendar/lightning/content/lightning.js
+++ b/calendar/lightning/content/lightning.js
@@ -70,16 +70,19 @@ pref("calendar.itip.updateInvitationForN
 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 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);
--- a/calendar/lightning/themes/common/lightning.css
+++ b/calendar/lightning/themes/common/lightning.css
@@ -106,8 +106,13 @@
 
 .imipDeleteButton {
     list-style-image: url(chrome://calendar-common/skin/icons/delete.svg);
 }
 
 .imipReconfirmButton {
     list-style-image: url(chrome://calendar-common/skin/icons/priority.svg);
 }
+
+.imipAcceptLabel {
+    font-weight: bold;
+    font-style: italic;
+}
--- a/calendar/locales/en-US/chrome/lightning/lightning.dtd
+++ b/calendar/locales/en-US/chrome/lightning/lightning.dtd
@@ -78,16 +78,23 @@
 <!ENTITY lightning.imipbar.btnTentative.label                               "Tentative">
 <!ENTITY lightning.imipbar.btnTentative2.tooltiptext                        "Accept event invitation tentatively">
 <!ENTITY lightning.imipbar.btnTentativeRecurrences.label                    "Tentative all">
 <!ENTITY lightning.imipbar.btnTentativeRecurrences2.tooltiptext             "Accept event invitation tentatively for all occurrences of the event">
 <!ENTITY lightning.imipbar.btnUpdate.label                                  "Update">
 <!ENTITY lightning.imipbar.btnUpdate.tooltiptext                            "Update event in calendar">
 <!ENTITY lightning.imipbar.description                                      "This message contains an invitation to an event.">
 
+<!ENTITY lightning.imipbar.btnSend.label                                    "Send a response now">
+<!ENTITY lightning.imipbar.btnSend.tooltiptext                              "Send a response to the organizer">
+<!ENTITY lightning.imipbar.btnSendSeries.tooltiptext                        "Send a response for the entire series to the organizer">
+<!ENTITY lightning.imipbar.btnDontSend.label                                "Do not send a response">
+<!ENTITY lightning.imipbar.btnDontSend.tooltiptext                          "Change your participation status without sending a response to the organizer">
+<!ENTITY lightning.imipbar.btnDontSendSeries.tooltiptext                    "Change your participation status for the series without sending a response to the organizer">
+
 <!-- Lightning specific keybindings -->
 <!ENTITY lightning.keys.event.showCalendar.key "C">
 <!ENTITY lightning.keys.event.showTasks.key "D">
 <!ENTITY lightning.keys.event.new "I">
 <!ENTITY lightning.keys.todo.new "D">
 
 <!-- Account Central page -->
 <!ENTITY lightning.acctCentral.newCalendar.label "Create a new calendar">
--- a/calendar/locales/en-US/chrome/lightning/lightning.properties
+++ b/calendar/locales/en-US/chrome/lightning/lightning.properties
@@ -124,18 +124,22 @@ imipBarCounterText=This message contains
 imipBarDisallowedCounterText=This message contains a counterproposal although you disallowed countering for this event.
 imipBarDeclineCounterText=This message contains a reply to your counterproposal.
 imipBarRefreshText=This message asks for an event update.
 imipBarPublishText=This message contains an event.
 imipBarRequestText=This message contains an invitation to an event.
 imipBarSentText=This message contains a sent event.
 imipBarSentButRemovedText=This message contains a sent out event that is not in your calendar anymore.
 imipBarUpdateText=This message contains an update to an existing event.
+imipBarUpdateMultipleText=This message contains updates to multiple existing events.
+imipBarUpdateSeriesText=This message contains an update to an existing series of events.
 imipBarAlreadyProcessedText=This message contains an event that has already been processed.
 imipBarProcessedNeedsAction=This message contains an event that you have not yet responded to.
+imipBarProcessedMultipleNeedsAction=This message contains multiple events that you have not yet responded to.
+imipBarProcessedSeriesNeedsAction=This message contains an event series that you have not yet responded to.
 imipBarReplyText=This message contains a reply to an invitation.
 imipBarReplyToNotExistingItem=This message contains a reply referring to an event that is not in your calendar.
 # LOCALIZATION_NOTE(imipBarReplyToRecentlyRemovedItem):
 # %1$S - datetime of deletion
 imipBarReplyToRecentlyRemovedItem=This message contains a reply referring to an event that was removed from your calendar at %1$S.
 imipBarUnsupportedText=This message contains an event that this version of Lightning cannot process.
 imipBarProcessingFailed=Processing message failed. Status: %1$S.
 imipBarNotWritable=No writable calendars are configured for invitations, please check the calendar properties.