diff --git a/calendar/lightning/content/imip-bar-eml.js b/calendar/lightning/content/imip-bar-eml.js deleted file mode 100644 index 039e0c36e7..0000000000 --- a/calendar/lightning/content/imip-bar-eml.js +++ /dev/null @@ -1,470 +0,0 @@ -/* 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/. */ - -Components.utils.import("resource://calendar/modules/calUtils.jsm"); -Components.utils.import("resource://calendar/modules/calItipUtils.jsm"); -Components.utils.import("resource://calendar/modules/calXMLUtils.jsm"); -Components.utils.import("resource://calendar/modules/ltnInvitationUtils.jsm"); -Components.utils.import("resource://gre/modules/Preferences.jsm"); -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); - -/** - * This bar lives inside the message window. - * Its lifetime is the lifetime of the main thunderbird message window. - */ -var ltnImipBar = { - - actionFunc: null, - itipItem: null, - foundItems: null, - msgOverlay: null, - - /** - * Thunderbird Message listener interface, hide the bar before we begin - */ - onStartHeaders: function() { - ltnImipBar.resetBar(); - }, - - /** - * Thunderbird Message listener interface - */ - onEndHeaders: function() { - - }, - - /** - * Load Handler called to initialize the imip bar - * NOTE: This function is called without a valid this-context! - */ - load: function() { - // Add a listener to gMessageListeners defined in msgHdrViewOverlay.js - gMessageListeners.push(ltnImipBar); - - // We need to extend the HideMessageHeaderPane function to also hide the - // message header pane. Otherwise, the imip bar will still be shown when - // changing folders. - ltnImipBar.tbHideMessageHeaderPane = HideMessageHeaderPane; - HideMessageHeaderPane = function() { - ltnImipBar.resetBar(); - ltnImipBar.tbHideMessageHeaderPane.apply(null, arguments); - }; - - // Set up our observers - Services.obs.addObserver(ltnImipBar, "onItipItemCreation", false); - }, - - /** - * Unload handler to clean up after the imip bar - * NOTE: This function is called without a valid this-context! - */ - unload: function() { - removeEventListener("messagepane-loaded", ltnImipBar.load, true); - removeEventListener("messagepane-unloaded", ltnImipBar.unload, true); - - ltnImipBar.resetBar(); - Services.obs.removeObserver(ltnImipBar, "onItipItemCreation"); - }, - - observe: function(subject, topic, state) { - if (topic == "onItipItemCreation") { - let itipItem = null; - let msgOverlay = null; - try { - if (!subject) { - let sinkProps = msgWindow.msgHeaderSink.properties; - // This property was set by lightningTextCalendarConverter.js - itipItem = sinkProps.getPropertyAsInterface("itipItem", - Components.interfaces.calIItipItem); - msgOverlay = sinkProps.getPropertyAsAUTF8String("msgOverlay"); - } - } catch (e) { - // This will throw on every message viewed that doesn't have the - // itipItem property set on it. So we eat the errors and move on. - - // XXX TODO: Only swallow the errors we need to. Throw all others. - } - if (!itipItem || !msgOverlay || !gMessageDisplay.displayedMessage) { - return; - } - - let imipMethod = gMessageDisplay.displayedMessage.getStringProperty("imip_method"); - cal.itip.initItemFromMsgData(itipItem, imipMethod, gMessageDisplay.displayedMessage); - - let imipBar = document.getElementById("imip-bar"); - imipBar.setAttribute("collapsed", "false"); - imipBar.setAttribute("label", cal.itip.getMethodText(itipItem.receivedMethod)); - - ltnImipBar.msgOverlay = msgOverlay; - - cal.itip.processItipItem(itipItem, ltnImipBar.setupOptions); - } - }, - - /** - * Hide the imip bar and reset the itip item. - */ - resetBar: function() { - document.getElementById("imip-bar").collapsed = true; - ltnImipBar.resetButtons(); - - // Clear our iMIP/iTIP stuff so it doesn't contain stale information. - cal.itip.cleanupItipItem(ltnImipBar.itipItem); - ltnImipBar.itipItem = null; - }, - - /** - * Resets all buttons and its menuitems, all buttons are hidden thereafter - */ - resetButtons: function() { - let buttons = ltnImipBar.getButtons(); - buttons.forEach(hideElement); - buttons.forEach(aButton => ltnImipBar.getMenuItems(aButton).forEach(showElement)); - }, - - /** - * Provides a list of all available buttons - */ - getButtons: function() { - let toolbarbuttons = document.getElementById("imip-view-toolbar") - .getElementsByTagName("toolbarbutton"); - return Array.from(toolbarbuttons); - }, - - /** - * Provides a list of available menuitems of a button - * - * @param aButton button node - */ - getMenuItems: function(aButton) { - let items = []; - let mitems = aButton.getElementsByTagName("menuitem"); - if (mitems != null && mitems.length > 0) { - for (let mitem of mitems) { - items.push(mitem); - } - } - return items; - }, - - /** - * Checks and converts button types based on available menuitems of the buttons - * to avoid dropdowns which are empty or only replicating the default button action - * Should be called once the buttons are set up - */ - conformButtonType: function() { - // check only needed on visible and not simple buttons - let buttons = ltnImipBar.getButtons() - .filter(aElement => aElement.hasAttribute("type") && !aElement.hidden); - // change button if appropriate - for (let button of buttons) { - let items = ltnImipBar.getMenuItems(button).filter(aItem => !aItem.hidden); - if (button.type == "menu" && items.length == 0) { - // hide non functional buttons - button.hidden = true; - } else if (button.type == "menu-button") { - if (items.length == 0 || - (items.length == 1 && - button.hasAttribute("oncommand") && - items[0].hasAttribute("oncommand") && - button.getAttribute("oncommand") - .endsWith(items[0].getAttribute("oncommand")))) { - // convert to simple button - button.removeAttribute("type"); - } - } - } - }, - - /** - * This is our callback function that is called each time the itip bar UI needs updating. - * NOTE: This function is called without a valid this-context! - * - * @param itipItem The iTIP item to set up for - * @param rc The status code from processing - * @param actionFunc The action function called for execution - * @param foundItems An array of items found while searching for the item - * in subscribed calendars - */ - setupOptions: function(itipItem, rc, actionFunc, foundItems) { - let imipBar = document.getElementById("imip-bar"); - let data = cal.itip.getOptionsText(itipItem, rc, actionFunc, foundItems); - - if (Components.isSuccessCode(rc)) { - ltnImipBar.itipItem = itipItem; - ltnImipBar.actionFunc = actionFunc; - ltnImipBar.foundItems = foundItems; - } - - // We need this to determine whether this is an outgoing or incoming message because - // Thunderbird doesn't provide a distinct flag on message level to do so. Relying on - // folder flags only may lead to false positives. - let isOutgoing = function(aMsgHdr) { - if (!aMsgHdr) { - return false; - } - let author = aMsgHdr.mime2DecodedAuthor; - let isSentFolder = aMsgHdr.folder && aMsgHdr.folder.flags & - Components.interfaces.nsMsgFolderFlags.SentMail; - if (author && isSentFolder) { - let accounts = MailServices.accounts; - for (let identity in fixIterator(accounts.allIdentities, - Components.interfaces.nsIMsgIdentity)) { - if (author.includes(identity.email) && !identity.fccReplyFollowsParent) { - return true; - } - } - } - return false; - }; - - // We override the bar label for sent out invitations and in case the event does not exist - // anymore, we also clear the buttons if any to avoid e.g. accept/decline buttons - if (isOutgoing(gMessageDisplay.displayedMessage)) { - if (ltnImipBar.foundItems && ltnImipBar.foundItems[0]) { - data.label = ltn.getString("lightning", "imipBarSentText"); - } else { - data = { - label: ltn.getString("lightning", "imipBarSentButRemovedText"), - buttons: [], - 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))); - // adjust button style if necessary - ltnImipBar.conformButtonType(); - ltnImipBar.displayModifications(); - }, - - /** - * Displays changes in case of invitation updates in invitation overlay - */ - displayModifications: function() { - if (!ltnImipBar.msgOverlay || !msgWindow || !ltnImipBar.foundItems || - !ltnImipBar.foundItems[0] || !ltnImipBar.itipItem) { - return; - } - - let msgOverlay = ltnImipBar.msgOverlay; - let diff = cal.itip.compare(ltnImipBar.itipItem.getItemList({})[0], ltnImipBar.foundItems[0]); - // displaying chnages is only needed if that is enabled, an item already exists and there are - // differences - if (diff != 0 && Preferences.get("calendar.itip.displayInvitationChanges", false)) { - let foundOverlay = ltn.invitation.createInvitationOverlay(ltnImipBar.foundItems[0], - ltnImipBar.itipItem); - let serializedOverlay = cal.xml.serializeDOM(foundOverlay); - let organizerId = ltnImipBar.itipItem.targetCalendar.getProperty("organizerId"); - if (diff == 1) { - // this is an update to previously accepted invitation - msgOverlay = ltn.invitation.compareInvitationOverlay(serializedOverlay, msgOverlay, - organizerId); - } else { - // this is a copy of a previously sent out invitation or a previous revision of a - // 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) { - if (cal.itip.promptCalendar(aActionFunc.method, aItipItem, aWindow)) { - let isDeclineCounter = aPartStat == "X-DECLINECOUNTER"; - // filter out fake partstats - if (aPartStat.startsWith("X-")) { - partStat = ""; - } - // hide the buttons now, to disable pressing them twice... - if (aPartStat == partStat) { - 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 - aItipItem.getItemList({}).forEach(aItem => { - // we can rely on the received itipItem to reply at this stage - // already, the checks have been done in cal.itip.processFoundItems - // when setting up the respective aActionFunc - let attendees = cal.getAttendeesBySender( - aItem.getAttendees({}), - aItipItem.sender - ); - let status = true; - if (attendees.length == 1 && ltnImipBar.foundItems && - ltnImipBar.foundItems.length) { - // we must return a message with the same sequence number as the - // counterproposal - to make it easy, we simply use the received - // item and just remove a comment, if any - try { - let item = aItem.clone(); - item.calendar = ltnImipBar.foundItems[0].calendar; - item.deleteProperty("COMMENT"); - // once we have full support to deal with for multiple items - // in a received invitation message, we should send this - // from outside outside of the forEach context - status = cal.itip.sendDeclineCounterMessage( - item, - "DECLINECOUNTER", - attendees, - { value: false } - ); - } catch (e) { - cal.ERROR(e); - status = false; - } - } else { - status = false; - } - if (!status) { - cal.ERROR("Failed to send DECLINECOUNTER reply!"); - } - }); - } - // For now, we just state the status for the user something very simple - let label = cal.itip.getCompleteText(aStatus, aOperationType); - imipBar.setAttribute("label", label); - - if (!Components.isSuccessCode(aStatus)) { - showError(label); - } - }, - onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) { - } - }; - - try { - aActionFunc(opListener, partStat); - } catch (exc) { - Components.utils.reportError(exc); - } - return true; - } - return false; - } - - let imipBar = document.getElementById("imip-bar"); - if (partStat == null) { - partStat = ""; - } - if (partStat == "X-SHOWDETAILS" || partStat == "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") { - // 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(); - } - let parsedProposal = ltn.invitation.parseCounter(proposedItem, item); - let potentialProposers = cal.getAttendeesBySender( - proposedItem.getAttendees({}), - ltnImipBar.itipItem.sender - ); - let proposingAttendee = potentialProposers.length == 1 ? - potentialProposers[0] : null; - if (proposingAttendee && - ["OK", "OUTDATED", "NOTLATESTUPDATE"].includes(parsedProposal.result.type)) { - counterProposal = { - attendee: proposingAttendee, - proposal: parsedProposal.differences, - oldVersion: parsedProposal.result == "OLDVERSION" || - parsedProposal.result == "NOTLATESTUPDATE", - onReschedule: () => { - imipBar.setAttribute( - "label", - ltn.getString("lightning", "imipBarCounterPreviousVersionText") - ); - // TODO: should we hide the buttons in this case, too? - } - }; - } else { - imipBar.setAttribute( - "label", - ltn.getString("lightning", "imipBarCounterErrorText") - ); - ltnImipBar.resetButtons(); - if (proposingAttendee) { - 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); - } - } else { - if (extendResponse) { - // 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") { - // 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); - } - }; - 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 false; - } -}; - -addEventListener("messagepane-loaded", ltnImipBar.load, true); -addEventListener("messagepane-unloaded", ltnImipBar.unload, true);