diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/components/compose/content/cloudAttachmentLinkManager.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mail/components/compose/content/cloudAttachmentLinkManager.js')
-rw-r--r-- | comm/mail/components/compose/content/cloudAttachmentLinkManager.js | 758 |
1 files changed, 758 insertions, 0 deletions
diff --git a/comm/mail/components/compose/content/cloudAttachmentLinkManager.js b/comm/mail/components/compose/content/cloudAttachmentLinkManager.js new file mode 100644 index 0000000000..9693f1aa8d --- /dev/null +++ b/comm/mail/components/compose/content/cloudAttachmentLinkManager.js @@ -0,0 +1,758 @@ +/* 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 MsgComposeCommands.js */ + +let { MsgUtils } = ChromeUtils.import( + "resource:///modules/MimeMessageUtils.jsm" +); + +var gCloudAttachmentLinkManager = { + init() { + this.cloudAttachments = []; + + let bucket = document.getElementById("attachmentBucket"); + bucket.addEventListener("attachments-removed", this); + bucket.addEventListener("attachment-converted-to-regular", this); + bucket.addEventListener("attachment-uploaded", this); + bucket.addEventListener("attachment-moved", this); + bucket.addEventListener("attachment-renamed", this); + + // If we're restoring a draft that has some attachments, + // check to see if any of them are marked to be sent via + // cloud, and if so, add them to our list. + for (let i = 0; i < bucket.getRowCount(); ++i) { + let attachment = bucket.getItemAtIndex(i).attachment; + if (attachment && attachment.sendViaCloud) { + this.cloudAttachments.push(attachment); + } + } + + gMsgCompose.RegisterStateListener(this); + }, + + NotifyComposeFieldsReady() {}, + NotifyComposeBodyReady() {}, + ComposeProcessDone() {}, + SaveInFolderDone() {}, + + async handleEvent(event) { + let mailDoc = document.getElementById("messageEditor").contentDocument; + + if ( + event.type == "attachment-renamed" || + event.type == "attachment-moved" + ) { + let cloudFileUpload = event.target.cloudFileUpload; + let items = []; + + let list = mailDoc.getElementById("cloudAttachmentList"); + if (list) { + items = list.getElementsByClassName("cloudAttachmentItem"); + } + + for (let item of items) { + // The original attachment is stored in the events detail property. + if (item.dataset.contentLocation == event.detail.contentLocation) { + item.replaceWith(await this._createNode(mailDoc, cloudFileUpload)); + } + } + if (event.type == "attachment-moved") { + await this._updateServiceProviderLinks(mailDoc); + } + } else if (event.type == "attachment-uploaded") { + if (this.cloudAttachments.length == 0) { + this._insertHeader(mailDoc); + } + + let cloudFileUpload = event.target.cloudFileUpload; + let attachment = event.target.attachment; + this.cloudAttachments.push(attachment); + await this._insertItem(mailDoc, cloudFileUpload); + } else if ( + event.type == "attachments-removed" || + event.type == "attachment-converted-to-regular" + ) { + let items = []; + let list = mailDoc.getElementById("cloudAttachmentList"); + if (list) { + items = list.getElementsByClassName("cloudAttachmentItem"); + } + + let attachments = Array.isArray(event.detail) + ? event.detail + : [event.detail]; + for (let attachment of attachments) { + // Remove the attachment from the message body. + if (list) { + for (let item of items) { + if (item.dataset.contentLocation == attachment.contentLocation) { + item.remove(); + } + } + } + + // Now, remove the attachment from our internal list. + let index = this.cloudAttachments.indexOf(attachment); + if (index != -1) { + this.cloudAttachments.splice(index, 1); + } + } + + await this._updateAttachmentCount(mailDoc); + await this._updateServiceProviderLinks(mailDoc); + + if (items.length == 0) { + if (list) { + list.remove(); + } + this._removeRoot(mailDoc); + } + } + }, + + /** + * Removes the root node for an attachment list in an HTML email. + * + * @param {Document} aDocument - the document to remove the root node from + */ + _removeRoot(aDocument) { + let header = aDocument.getElementById("cloudAttachmentListRoot"); + if (header) { + header.remove(); + } + }, + + /** + * Given some node, returns the textual HTML representation for the node + * and its children. + * + * @param {Document} aDocument - the document that the node is embedded in + * @param {DOMNode} aNode - the node to get the textual representation from + */ + _getHTMLRepresentation(aDocument, aNode) { + let tmp = aDocument.createElement("p"); + tmp.appendChild(aNode); + return tmp.innerHTML; + }, + + /** + * Returns the plain text equivalent of the given HTML markup, ready to be + * inserted into a compose editor. + * + * @param {string} aMarkup - the HTML markup that should be converted + */ + _getTextRepresentation(aMarkup) { + return MsgUtils.convertToPlainText(aMarkup, true).replaceAll("\r\n", "\n"); + }, + + /** + * Generates an appropriately styled link. + * + * @param {Document} aDocument - the document to append the link to - doesn't + * actually get appended, but is used to generate the anchor node + * @param {string} aContent - the textual content of the link + * @param {string} aHref - the HREF attribute for the generated link + * @param {string} aColor - the CSS color string for the link + */ + _generateLink(aDocument, aContent, aHref, aColor) { + let link = aDocument.createElement("a"); + link.href = aHref; + link.textContent = aContent; + link.style.cssText = `color: ${aColor} !important`; + return link; + }, + + _findInsertionPoint(aDocument) { + let mailBody = aDocument.querySelector("body"); + let editor = GetCurrentEditor(); + let selection = editor.selection; + + let childNodes = mailBody.childNodes; + let childToInsertAfter, childIndex; + + // First, search for any text nodes that are immediate children of + // the body. If we find any, we'll insert after those. + for (childIndex = childNodes.length - 1; childIndex >= 0; childIndex--) { + if (childNodes[childIndex].nodeType == Node.TEXT_NODE) { + childToInsertAfter = childNodes[childIndex]; + break; + } + } + + if (childIndex != -1) { + selection.collapse( + childToInsertAfter, + childToInsertAfter.nodeValue ? childToInsertAfter.nodeValue.length : 0 + ); + if ( + childToInsertAfter.nodeValue && + childToInsertAfter.nodeValue.length > 0 + ) { + editor.insertLineBreak(); + } + editor.insertLineBreak(); + return; + } + + // If there's a signature, let's get a hold of it now. + let signature = mailBody.querySelector(".moz-signature"); + + // Are we replying? + let replyCitation = mailBody.querySelector(".moz-cite-prefix"); + if (replyCitation) { + if (gCurrentIdentity && gCurrentIdentity.replyOnTop == 0) { + // Replying below quote - we'll select the point right before + // the signature. If there's no signature, we'll just use the + // last node. + if (signature && signature.previousSibling) { + selection.collapse( + mailBody, + Array.from(childNodes).indexOf(signature.previousSibling) + ); + } else { + selection.collapse(mailBody, childNodes.length - 1); + editor.insertLineBreak(); + + if (!gMsgCompose.composeHTML) { + editor.insertLineBreak(); + } + + selection.collapse(mailBody, childNodes.length - 2); + } + } else if (replyCitation.previousSibling) { + // Replying above quote + let nodeIndex = Array.from(childNodes).indexOf( + replyCitation.previousSibling + ); + if (nodeIndex <= 0) { + editor.insertLineBreak(); + nodeIndex = 1; + } + selection.collapse(mailBody, nodeIndex); + } else { + editor.beginningOfDocument(); + editor.insertLineBreak(); + } + return; + } + + // Are we forwarding? + let forwardBody = mailBody.querySelector(".moz-forward-container"); + if (forwardBody) { + if (forwardBody.previousSibling) { + let nodeIndex = Array.from(childNodes).indexOf( + forwardBody.previousSibling + ); + if (nodeIndex <= 0) { + editor.insertLineBreak(); + nodeIndex = 1; + } + // If we're forwarding, insert just before the forward body. + selection.collapse(mailBody, nodeIndex); + } else { + // Just insert after a linebreak at the top. + editor.beginningOfDocument(); + editor.insertLineBreak(); + selection.collapse(mailBody, 1); + } + return; + } + + // If we haven't figured it out at this point, let's see if there's a + // signature, and just insert before it. + if (signature && signature.previousSibling) { + let nodeIndex = Array.from(childNodes).indexOf(signature.previousSibling); + if (nodeIndex <= 0) { + editor.insertLineBreak(); + nodeIndex = 1; + } + selection.collapse(mailBody, nodeIndex); + return; + } + + // If we haven't figured it out at this point, let's just put it + // at the bottom of the message body. If the "bottom" is also the top, + // then we'll insert a linebreak just above it. + let nodeIndex = childNodes.length - 1; + if (nodeIndex <= 0) { + editor.insertLineBreak(); + nodeIndex = 1; + } + selection.collapse(mailBody, nodeIndex); + }, + + /** + * Attempts to find any elements with an id in aIDs, and sets those elements + * id attribute to the empty string, freeing up the ids for later use. + * + * @param {Document} aDocument - the document to search for the elements + * @param {string[]} aIDs - an array of id strings + */ + _resetNodeIDs(aDocument, aIDs) { + for (let id of aIDs) { + let node = aDocument.getElementById(id); + if (node) { + node.id = ""; + } + } + }, + + /** + * Insert the header for the cloud attachment list, which we'll use to + * as an insertion point for the individual cloud attachments. + * + * @param {Document} aDocument - the document to insert the header into + */ + _insertHeader(aDocument) { + // If there already exists a cloudAttachmentListRoot, + // cloudAttachmentListHeader, cloudAttachmentListFooter or + // cloudAttachmentList in the document, strip them of their IDs so that we + // don't conflict with them. + this._resetNodeIDs(aDocument, [ + "cloudAttachmentListRoot", + "cloudAttachmentListHeader", + "cloudAttachmentList", + "cloudAttachmentListFooter", + ]); + + let editor = GetCurrentEditor(); + let selection = editor.selection; + let originalAnchor = selection.anchorNode; + let originalOffset = selection.anchorOffset; + + // Save off the selection ranges so we can restore them later. + let ranges = []; + for (let i = 0; i < selection.rangeCount; i++) { + ranges.push(selection.getRangeAt(i)); + } + + this._findInsertionPoint(aDocument); + + let root = editor.createElementWithDefaults("div"); + let header = editor.createElementWithDefaults("div"); + let list = editor.createElementWithDefaults("div"); + let footer = editor.createElementWithDefaults("div"); + + if (gMsgCompose.composeHTML) { + root.style.padding = "15px"; + root.style.backgroundColor = "#D9EDFF"; + + header.style.marginBottom = "15px"; + + list = editor.createElementWithDefaults("ul"); + list.style.backgroundColor = "#FFFFFF"; + list.style.padding = "15px"; + list.style.listStyleType = "none"; + list.display = "inline-block"; + } + + root.id = "cloudAttachmentListRoot"; + header.id = "cloudAttachmentListHeader"; + list.id = "cloudAttachmentList"; + footer.id = "cloudAttachmentListFooter"; + + // It's really quite strange, but if we don't set + // the innerHTML of each element to be non-empty, then + // the nodes fail to be added to the compose window. + root.innerHTML = " "; + header.innerHTML = " "; + list.innerHTML = " "; + footer.innerHTML = " "; + + root.appendChild(header); + root.appendChild(list); + root.appendChild(footer); + editor.insertElementAtSelection(root, false); + if (!root.previousSibling || root.previousSibling.localName == "span") { + root.parentNode.insertBefore(editor.document.createElement("br"), root); + } + + // Remove the space, which would end up in the plain text converted + // version. + list.innerHTML = ""; + selection.collapse(originalAnchor, originalOffset); + + // Restore the selection ranges. + for (let range of ranges) { + selection.addRange(range); + } + }, + + /** + * Updates the count of how many attachments have been added + * in HTML emails. + * + * @param {Document} aDocument - the document that contains the header node + */ + async _updateAttachmentCount(aDocument) { + let header = aDocument.getElementById("cloudAttachmentListHeader"); + if (!header) { + return; + } + + let entries = aDocument.querySelectorAll( + "#cloudAttachmentList > .cloudAttachmentItem" + ); + + header.textContent = await l10nCompose.formatValue( + "cloud-file-count-header", + { + count: entries.length, + } + ); + }, + + /** + * Updates the service provider links in the footer. + * + * @param {Document} aDocument - the document that contains the footer node + */ + async _updateServiceProviderLinks(aDocument) { + let footer = aDocument.getElementById("cloudAttachmentListFooter"); + if (!footer) { + return; + } + + let providers = []; + let entries = aDocument.querySelectorAll( + "#cloudAttachmentList > .cloudAttachmentItem" + ); + for (let entry of entries) { + if (!entry.dataset.serviceUrl) { + continue; + } + + let link_markup = this._generateLink( + aDocument, + entry.dataset.serviceName, + entry.dataset.serviceUrl, + "dark-grey" + ).outerHTML; + + if (!providers.includes(link_markup)) { + providers.push(link_markup); + } + } + + let content = ""; + if (providers.length == 1) { + content = await l10nCompose.formatValue( + "cloud-file-service-provider-footer-single", + { + link: providers[0], + } + ); + } else if (providers.length > 1) { + let lastLink = providers.pop(); + let firstLinks = providers.join(", "); + content = await l10nCompose.formatValue( + "cloud-file-service-provider-footer-multiple", + { + firstLinks, + lastLink, + } + ); + } + + if (gMsgCompose.composeHTML) { + // eslint-disable-next-line no-unsanitized/property + footer.innerHTML = content; + } else { + footer.textContent = this._getTextRepresentation(content); + } + }, + + /** + * Insert the information for a cloud attachment. + * + * @param {Document} aDocument - the document to insert the item into + * @param {CloudFileTemplate} aCloudFileUpload - object with information about + * the uploaded file + */ + async _insertItem(aDocument, aCloudFileUpload) { + let list = aDocument.getElementById("cloudAttachmentList"); + + if (!list) { + this._insertHeader(aDocument); + list = aDocument.getElementById("cloudAttachmentList"); + } + list.appendChild(await this._createNode(aDocument, aCloudFileUpload)); + await this._updateAttachmentCount(aDocument); + await this._updateServiceProviderLinks(aDocument); + }, + + /** + * @typedef CloudFileDate + * @property {integer} timestamp - milliseconds since epoch + * @property {DateTimeFormat} format - format object of Intl.DateTimeFormat + */ + + /** + * @typedef CloudFileTemplate + * @property {string} serviceName - name of the upload service provider + * @property {string} serviceIcon - icon of the upload service provider + * @property {string} serviceUrl - web interface of the upload service provider + * @property {boolean} downloadPasswordProtected - link is password protected + * @property {integer} downloadLimit - download limit of the link + * @property {CloudFileDate} downloadExpiryDate - expiry date of the link + */ + + /** + * Create the link node for a cloud attachment. + * + * @param {Document} aDocument - the document to insert the item into + * @param {CloudFileTemplate} aCloudFileUpload - object with information about + * the uploaded file + * @param {boolean} composeHTML - override gMsgCompose.composeHTML + */ + async _createNode( + aDocument, + aCloudFileUpload, + composeHTML = gMsgCompose.composeHTML + ) { + const iconSize = 32; + const locales = { + service: 0, + size: 1, + link: 2, + "password-protected-link": 3, + "expiry-date": 4, + "download-limit": 5, + "tooltip-password-protected-link": 6, + }; + + let l10n_values = await l10nCompose.formatValues([ + { id: "cloud-file-template-service-name" }, + { id: "cloud-file-template-size" }, + { id: "cloud-file-template-link" }, + { id: "cloud-file-template-password-protected-link" }, + { id: "cloud-file-template-expiry-date" }, + { id: "cloud-file-template-download-limit" }, + { id: "cloud-file-tooltip-password-protected-link" }, + ]); + + let node = aDocument.createElement("li"); + node.style.border = "1px solid #CDCDCD"; + node.style.borderRadius = "5px"; + node.style.marginTop = "10px"; + node.style.marginBottom = "10px"; + node.style.padding = "15px"; + node.style.display = "grid"; + node.style.gridTemplateColumns = "0fr 1fr 0fr 0fr"; + node.style.alignItems = "center"; + + const statsRow = (name, content, contentLink) => { + let entry = aDocument.createElement("span"); + entry.style.gridColumn = `2 / span 3`; + entry.style.fontSize = "small"; + + let description = aDocument.createElement("span"); + description.style.color = "dark-grey"; + description.textContent = `${l10n_values[locales[name]]} `; + entry.appendChild(description); + + let value; + if (composeHTML && contentLink) { + value = this._generateLink(aDocument, content, contentLink, "#595959"); + } else { + value = aDocument.createElement("span"); + value.style.color = "#595959"; + value.textContent = content; + } + value.classList.add(`cloudfile-${name}`); + entry.appendChild(value); + + entry.appendChild(aDocument.createElement("br")); + return entry; + }; + + const serviceRow = () => { + let service = aDocument.createDocumentFragment(); + + let description = aDocument.createElement("span"); + description.style.display = "none"; + description.textContent = `${l10n_values[locales.service]} `; + service.appendChild(description); + + let providerName = aDocument.createElement("span"); + providerName.style.gridArea = "1 / 4"; + providerName.style.color = "#595959"; + providerName.style.fontSize = "small"; + providerName.textContent = aCloudFileUpload.serviceName; + providerName.classList.add("cloudfile-service-name"); + service.appendChild(providerName); + + service.appendChild(aDocument.createElement("br")); + return service; + }; + + // If this message is send in plain text only, do not add a link to the file + // name. + let name = aDocument.createElement("span"); + name.textContent = aCloudFileUpload.name; + if (composeHTML) { + name = this._generateLink( + aDocument, + aCloudFileUpload.name, + aCloudFileUpload.url, + "#0F7EDB" + ); + name.setAttribute("moz-do-not-send", "true"); + name.style.gridArea = "1 / 2"; + } + name.classList.add("cloudfile-name"); + node.appendChild(name); + + let paperclip = aDocument.createElement("img"); + paperclip.classList.add("paperClipIcon"); + paperclip.style.gridArea = "1 / 1"; + paperclip.alt = ""; + paperclip.style.marginRight = "5px"; + paperclip.width = `${iconSize}`; + paperclip.height = `${iconSize}`; + if (aCloudFileUpload.downloadPasswordProtected) { + paperclip.title = l10n_values[locales["tooltip-password-protected-link"]]; + paperclip.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIfSURBVFhH7ZfLK0RRHMfvNd6PMV4Lj5UkO5bslJIdf4ClRw2TlY2yt2EhsZO9DYoFoiSvJBZkI6SsNMyIiLnH93vmXDF5HNe9pHzqM797fufMPb+Zc4Z7jC+QBnvgJryD93AddkH2eUop3IPiHXdgCfSEdLgLOdE+bIFFSl4zZxeRAl2HXzsn2IIZTCTAHPs4hsvhOlxz3rxRtt6GfRyzJlsucw1582zZehv2cUxEtlyGN6afkThuFa7EL7+H0wK03pek4q/xJwtYVv4YumurO+4V/3vgvwAvC5iHTfHL9zFV/Ah7J9tjE9s2r/K3YwWlD8IaREP+ExPCWBDJVl+gM3LEto0nBURHCiuNpBiflvLjqWcufDFfdVbo4ly1PVoC0xrAaz4qnLdiVjk1hVhArvDRFxuSYxQeFSAaGHzCbAuEIsf0URjtsithX3i1Cf18yewKn8kWyOu+OlWXuSpKnBRwpWKxioTXi7BCtr6Ak004BZvhJAwyAUZhb3Q0bwKxXmY+xVzyB8MNOgXwE/NrC0A+clXBDZV7iYkC7GK18AcvTZ0lOFGRE5NDWAtn4A28hdPQEToFcG1Jq4qERXAZ+DCaBXk+cIROAePQgh2whgk30SngAA7CVDgLq6Fr6P4M++Ec5PmPp6BhWAdzIA+m3BOO0C2AJ2GuMyfme0KQp6Ao5EmZf/fLDGFuI2oi+EEcUQm5JDywhpWc2MFGNIwn/WmcKhqF50UAAAAASUVORK5CYII="; + } else { + paperclip.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAVFJREFUWIXtl8FKw0AQhj8EbQ/p0Ut8AVEPgYLUB+i5L6J9E0Wtr1HPgl48WU8K1Tfw4LktxUAhHvZfiMXUbdhVhB0Yms78M/NldwkJuFsD6AMjYCYfASfKBbUd4BkoKvxJmiDWKA1/AXrAtrynmIUIshJ9DXgEmt/km8oVwHEIANu8u0LTleYhBMBUzZMVmkSaSQgAe9DW1d3L/wzAqW6jJpQ3+5cA3vbW1Vz3Np6BCBABIkAE+DWAmX7TUixdynm15Wf6jf5fa3Cq60K5qrraNuHrK1kbmJcGWJ8rB9DC4yvaq5odlmK7wBB4lw8Vs9ZRzdgHwLmaXa5RM1DNmQ+AA2ABfACZgz4DctXs+QAAuMLc0dsPEJk0BXDhazjAFnCnxjlmiTuYg5kAR4rl0twCmz4BLMQAs7RVH6kLzJ17H162fczhGmO+mqa6PqXGnn8CxMN0PcC9DrQAAAAASUVORK5CYII="; + } + node.appendChild(paperclip); + + let serviceIcon = aDocument.createElement("img"); + serviceIcon.classList.add("cloudfile-service-icon"); + serviceIcon.style.gridArea = "1 / 3"; + serviceIcon.alt = ""; + serviceIcon.style.margin = "0 5px"; + serviceIcon.width = `${iconSize}`; + serviceIcon.height = `${iconSize}`; + node.appendChild(serviceIcon); + + if (aCloudFileUpload.serviceIcon) { + if (!/^(chrome|moz-extension):\/\//i.test(aCloudFileUpload.serviceIcon)) { + serviceIcon.src = aCloudFileUpload.serviceIcon; + } else { + try { + // Let's use the goodness from MsgComposeCommands.js since we're + // sitting right in a compose window. + serviceIcon.src = window.loadBlockedImage( + aCloudFileUpload.serviceIcon, + true + ); + } catch (e) { + // Couldn't load the referenced image. + console.error(e); + } + } + } + node.appendChild(aDocument.createElement("br")); + + node.appendChild( + statsRow("size", gMessenger.formatFileSize(aCloudFileUpload.size)) + ); + + if (aCloudFileUpload.downloadExpiryDate) { + node.appendChild( + statsRow( + "expiry-date", + new Date( + aCloudFileUpload.downloadExpiryDate.timestamp + ).toLocaleString( + undefined, + aCloudFileUpload.downloadExpiryDate.format || { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + timeZoneName: "short", + } + ) + ) + ); + } + + if (aCloudFileUpload.downloadLimit) { + node.appendChild( + statsRow("download-limit", aCloudFileUpload.downloadLimit) + ); + } + + if (composeHTML || aCloudFileUpload.serviceUrl) { + node.appendChild(serviceRow()); + } + + let linkElementLocaleId = aCloudFileUpload.downloadPasswordProtected + ? "password-protected-link" + : "link"; + node.appendChild( + statsRow(linkElementLocaleId, aCloudFileUpload.url, aCloudFileUpload.url) + ); + + // An extra line break is needed for the converted plain text version, if it + // should have a gap between its <li> elements. + if (composeHTML) { + node.appendChild(aDocument.createElement("br")); + } + + // Generate the plain text version from the HTML. The used method needs a <ul> + // element wrapped around the <li> element to produce the correct content. + if (!composeHTML) { + let ul = aDocument.createElement("ul"); + ul.appendChild(node); + node = aDocument.createElement("p"); + node.textContent = this._getTextRepresentation(ul.outerHTML); + } + + node.className = "cloudAttachmentItem"; + node.dataset.contentLocation = aCloudFileUpload.url; + node.dataset.serviceName = aCloudFileUpload.serviceName; + node.dataset.serviceUrl = aCloudFileUpload.serviceUrl; + return node; + }, + + /** + * Event handler for when mail is sent. For mail that is being sent + * (and not saved!), find any cloudAttachmentList* nodes that we've created, + * and strip their IDs out. That way, if the receiving user replies by + * sending some BigFiles, we don't run into ID conflicts. + */ + send(aEvent) { + let msgType = parseInt(aEvent.target.getAttribute("msgtype")); + + if ( + msgType == Ci.nsIMsgCompDeliverMode.Now || + msgType == Ci.nsIMsgCompDeliverMode.Later || + msgType == Ci.nsIMsgCompDeliverMode.Background + ) { + const kIDs = [ + "cloudAttachmentListRoot", + "cloudAttachmentListHeader", + "cloudAttachmentList", + "cloudAttachmentListFooter", + ]; + let mailDoc = document.getElementById("messageEditor").contentDocument; + + for (let id of kIDs) { + let element = mailDoc.getElementById(id); + if (element) { + element.removeAttribute("id"); + } + } + } + }, +}; + +window.addEventListener( + "compose-window-init", + gCloudAttachmentLinkManager.init.bind(gCloudAttachmentLinkManager), + true +); +window.addEventListener( + "compose-send-message", + gCloudAttachmentLinkManager.send.bind(gCloudAttachmentLinkManager), + true +); |