diff options
Diffstat (limited to 'comm/mail/components/compose/content/editorUtilities.js')
-rw-r--r-- | comm/mail/components/compose/content/editorUtilities.js | 1015 |
1 files changed, 1015 insertions, 0 deletions
diff --git a/comm/mail/components/compose/content/editorUtilities.js b/comm/mail/components/compose/content/editorUtilities.js new file mode 100644 index 0000000000..3af6810c9c --- /dev/null +++ b/comm/mail/components/compose/content/editorUtilities.js @@ -0,0 +1,1015 @@ +/* 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 editor.js */ + +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Each editor window must include this file +// Variables shared by all dialogs: + +// Object to attach commonly-used widgets (all dialogs should use this) +var gDialog = {}; + +var kOutputEncodeBasicEntities = + Ci.nsIDocumentEncoder.OutputEncodeBasicEntities; +var kOutputEncodeHTMLEntities = Ci.nsIDocumentEncoder.OutputEncodeHTMLEntities; +var kOutputEncodeLatin1Entities = + Ci.nsIDocumentEncoder.OutputEncodeLatin1Entities; +var kOutputEncodeW3CEntities = Ci.nsIDocumentEncoder.OutputEncodeW3CEntities; +var kOutputFormatted = Ci.nsIDocumentEncoder.OutputFormatted; +var kOutputLFLineBreak = Ci.nsIDocumentEncoder.OutputLFLineBreak; +var kOutputSelectionOnly = Ci.nsIDocumentEncoder.OutputSelectionOnly; +var kOutputWrap = Ci.nsIDocumentEncoder.OutputWrap; + +var gStringBundle; +var gFilePickerDirectory; + +/** *********** Message dialogs */ + +// Optional: Caller may supply text to substitute for "Ok" and/or "Cancel" +function ConfirmWithTitle(title, message, okButtonText, cancelButtonText) { + let okFlag = okButtonText + ? Services.prompt.BUTTON_TITLE_IS_STRING + : Services.prompt.BUTTON_TITLE_OK; + let cancelFlag = cancelButtonText + ? Services.prompt.BUTTON_TITLE_IS_STRING + : Services.prompt.BUTTON_TITLE_CANCEL; + + return ( + Services.prompt.confirmEx( + window, + title, + message, + okFlag * Services.prompt.BUTTON_POS_0 + + cancelFlag * Services.prompt.BUTTON_POS_1, + okButtonText, + cancelButtonText, + null, + null, + { value: 0 } + ) == 0 + ); +} + +/** *********** String Utilities */ + +function GetString(name) { + if (!gStringBundle) { + try { + gStringBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/editor.properties" + ); + } catch (ex) {} + } + if (gStringBundle) { + try { + return gStringBundle.GetStringFromName(name); + } catch (e) {} + } + return null; +} + +function GetFormattedString(aName, aVal) { + if (!gStringBundle) { + try { + gStringBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/editor.properties" + ); + } catch (ex) {} + } + if (gStringBundle) { + try { + return gStringBundle.formatStringFromName(aName, [aVal]); + } catch (e) {} + } + return null; +} + +function TrimStringLeft(string) { + if (!string) { + return ""; + } + return string.trimLeft(); +} + +function TrimStringRight(string) { + if (!string) { + return ""; + } + return string.trimRight(); +} + +// Remove whitespace from both ends of a string +function TrimString(string) { + if (!string) { + return ""; + } + return string.trim(); +} + +function TruncateStringAtWordEnd(string, maxLength, addEllipses) { + // Return empty if string is null, undefined, or the empty string + if (!string) { + return ""; + } + + // We assume they probably don't want whitespace at the beginning + string = string.trimLeft(); + if (string.length <= maxLength) { + return string; + } + + // We need to truncate the string to maxLength or fewer chars + if (addEllipses) { + maxLength -= 3; + } + string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1"); + + if (string.length > maxLength) { + string = string.slice(0, maxLength); + } + + if (addEllipses) { + string += "..."; + } + return string; +} + +// Replace all whitespace characters with supplied character +// E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars +// Use charReplace = "_" when you don't want spaces (like in a URL) +function ReplaceWhitespace(string, charReplace) { + return string.trim().replace(/\s+/g, charReplace); +} + +// Replace whitespace with "_" and allow only HTML CDATA +// characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".", +// and characters above ASCII 127 +function ConvertToCDATAString(string) { + return string + .replace(/\s+/g, "_") + .replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g, ""); +} + +function GetSelectionAsText() { + try { + return GetCurrentEditor().outputToString( + "text/plain", + kOutputSelectionOnly + ); + } catch (e) {} + + return ""; +} + +/** *********** Get Current Editor and associated interfaces or info */ + +function GetCurrentEditor() { + // Get the active editor from the <editor> tag + // XXX This will probably change if we support > 1 editor in main Composer window + // (e.g. a plaintext editor for HTMLSource) + + // For dialogs: Search up parent chain to find top window with editor + var editor; + try { + var editorElement = GetCurrentEditorElement(); + editor = editorElement.getEditor(editorElement.contentWindow); + + // Do QIs now so editor users won't have to figure out which interface to use + // Using "instanceof" does the QI for us. + editor instanceof Ci.nsIHTMLEditor; + } catch (e) { + dump(e) + "\n"; + } + + return editor; +} + +function GetCurrentTableEditor() { + var editor = GetCurrentEditor(); + return editor && editor instanceof Ci.nsITableEditor ? editor : null; +} + +function GetCurrentEditorElement() { + var tmpWindow = window; + + do { + // Get the <editor> element(s) + let editorItem = tmpWindow.document.querySelector("editor"); + + // This will change if we support > 1 editor element + if (editorItem) { + return editorItem; + } + + tmpWindow = tmpWindow.opener; + } while (tmpWindow); + + return null; +} + +function GetCurrentCommandManager() { + try { + return GetCurrentEditorElement().commandManager; + } catch (e) { + dump(e) + "\n"; + } + + return null; +} + +function GetCurrentEditorType() { + try { + return GetCurrentEditorElement().editortype; + } catch (e) { + dump(e) + "\n"; + } + + return ""; +} + +/** + * Gets the editor's spell checker. Could return null if there are no + * dictionaries installed. + * + * @returns {nsIInlineSpellChecker?} + */ +function GetCurrentEditorSpellChecker() { + try { + return GetCurrentEditor().getInlineSpellChecker(true); + } catch (ex) {} + return null; +} + +function IsHTMLEditor() { + // We don't have an editorElement, just return false + if (!GetCurrentEditorElement()) { + return false; + } + + var editortype = GetCurrentEditorType(); + switch (editortype) { + case "html": + case "htmlmail": + return true; + + case "text": + case "textmail": + return false; + + default: + dump("INVALID EDITOR TYPE: " + editortype + "\n"); + break; + } + return false; +} + +function PageIsEmptyAndUntouched() { + return IsDocumentEmpty() && !IsDocumentModified() && !IsHTMLSourceChanged(); +} + +function IsInHTMLSourceMode() { + return gEditorDisplayMode == kDisplayModeSource; +} + +// are we editing HTML (i.e. neither in HTML source mode, nor editing a text file) +function IsEditingRenderedHTML() { + return IsHTMLEditor() && !IsInHTMLSourceMode(); +} + +function IsDocumentEditable() { + try { + return GetCurrentEditor().isDocumentEditable; + } catch (e) {} + return false; +} + +function IsDocumentEmpty() { + try { + return GetCurrentEditor().documentIsEmpty; + } catch (e) {} + return false; +} + +function IsDocumentModified() { + try { + return GetCurrentEditor().documentModified; + } catch (e) {} + return false; +} + +function IsHTMLSourceChanged() { + // gSourceTextEditor will not be defined if we're just a text editor. + return gSourceTextEditor ? gSourceTextEditor.documentModified : false; +} + +function newCommandParams() { + try { + return Cu.createCommandParams(); + } catch (e) { + dump("error thrown in newCommandParams: " + e + "\n"); + } + return null; +} + +/** *********** General editing command utilities */ + +function GetDocumentTitle() { + try { + return GetCurrentEditorElement().contentDocument.title; + } catch (e) {} + + return ""; +} + +function SetDocumentTitle(title) { + try { + GetCurrentEditorElement().contentDocument.title = title; + + // Update window title (doesn't work if called from a dialog) + if ("UpdateWindowTitle" in window) { + window.UpdateWindowTitle(); + } + } catch (e) {} +} + +function EditorGetTextProperty( + property, + attribute, + value, + firstHas, + anyHas, + allHas +) { + try { + return GetCurrentEditor().getInlinePropertyWithAttrValue( + property, + attribute, + value, + firstHas, + anyHas, + allHas + ); + } catch (e) {} +} + +function EditorSetTextProperty(property, attribute, value) { + try { + GetCurrentEditor().setInlineProperty(property, attribute, value); + if ("gContentWindow" in window) { + window.gContentWindow.focus(); + } + } catch (e) {} +} + +function EditorRemoveTextProperty(property, attribute) { + try { + GetCurrentEditor().removeInlineProperty(property, attribute); + if ("gContentWindow" in window) { + window.gContentWindow.focus(); + } + } catch (e) {} +} + +/** *********** Element enbabling/disabling */ + +// this function takes an elementID and a flag +// if the element can be found by ID, then it is either enabled (by removing "disabled" attr) +// or disabled (setAttribute) as specified in the "doEnable" parameter +function SetElementEnabledById(elementID, doEnable) { + SetElementEnabled(document.getElementById(elementID), doEnable); +} + +function SetElementEnabled(element, doEnable) { + if (element) { + if (doEnable) { + element.removeAttribute("disabled"); + } else { + element.setAttribute("disabled", "true"); + } + } else { + dump("Element not found in SetElementEnabled\n"); + } +} + +/** *********** Services / Prefs */ + +function GetFileProtocolHandler() { + let handler = Services.io.getProtocolHandler("file"); + return handler.QueryInterface(Ci.nsIFileProtocolHandler); +} + +function SetStringPref(aPrefName, aPrefValue) { + try { + Services.prefs.setStringPref(aPrefName, aPrefValue); + } catch (e) {} +} + +// Set initial directory for a filepicker from URLs saved in prefs +function SetFilePickerDirectory(filePicker, fileType) { + if (filePicker) { + try { + // Save current directory so we can reset it in SaveFilePickerDirectory + gFilePickerDirectory = filePicker.displayDirectory; + + let location = Services.prefs.getComplexValue( + "editor.lastFileLocation." + fileType, + Ci.nsIFile + ); + if (location) { + filePicker.displayDirectory = location; + } + } catch (e) {} + } +} + +// Save the directory of the selected file to prefs +function SaveFilePickerDirectory(filePicker, fileType) { + if (filePicker && filePicker.file) { + try { + var fileDir; + if (filePicker.file.parent) { + fileDir = filePicker.file.parent.QueryInterface(Ci.nsIFile); + } + + Services.prefs.setComplexValue( + "editor.lastFileLocation." + fileType, + Ci.nsIFile, + fileDir + ); + + Services.prefs.savePrefFile(null); + } catch (e) {} + } + + // Restore the directory used before SetFilePickerDirectory was called; + // This reduces interference with Browser and other module directory defaults + if (gFilePickerDirectory) { + filePicker.displayDirectory = gFilePickerDirectory; + } + + gFilePickerDirectory = null; +} + +function GetDefaultBrowserColors() { + var colors = { + TextColor: 0, + BackgroundColor: 0, + LinkColor: 0, + ActiveLinkColor: 0, + VisitedLinkColor: 0, + }; + var useSysColors = Services.prefs.getBoolPref( + "browser.display.use_system_colors", + false + ); + + if (!useSysColors) { + colors.TextColor = Services.prefs.getCharPref( + "browser.display.foreground_color", + 0 + ); + colors.BackgroundColor = Services.prefs.getCharPref( + "browser.display.background_color", + 0 + ); + } + // Use OS colors for text and background if explicitly asked or pref is not set + if (!colors.TextColor) { + colors.TextColor = "windowtext"; + } + + if (!colors.BackgroundColor) { + colors.BackgroundColor = "window"; + } + + colors.LinkColor = Services.prefs.getCharPref("browser.anchor_color"); + colors.ActiveLinkColor = Services.prefs.getCharPref("browser.active_color"); + colors.VisitedLinkColor = Services.prefs.getCharPref("browser.visited_color"); + + return colors; +} + +/** *********** URL handling */ + +function TextIsURI(selectedText) { + return ( + selectedText && + /^http:\/\/|^https:\/\/|^file:\/\/|^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|^ldaps:|^gopher:|^finger:|^javascript:/i.test( + selectedText + ) + ); +} + +function IsUrlAboutBlank(urlString) { + return urlString.startsWith("about:blank"); +} + +function MakeRelativeUrl(url) { + let inputUrl = url.trim(); + if (!inputUrl) { + return inputUrl; + } + + // Get the filespec relative to current document's location + // NOTE: Can't do this if file isn't saved yet! + var docUrl = GetDocumentBaseUrl(); + var docScheme = GetScheme(docUrl); + + // Can't relativize if no doc scheme (page hasn't been saved) + if (!docScheme) { + return inputUrl; + } + + var urlScheme = GetScheme(inputUrl); + + // Do nothing if not the same scheme or url is already relativized + if (docScheme != urlScheme) { + return inputUrl; + } + + // Host must be the same + var docHost = GetHost(docUrl); + var urlHost = GetHost(inputUrl); + if (docHost != urlHost) { + return inputUrl; + } + + // Get just the file path part of the urls + // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ? + let docPath = Services.io.newURI( + docUrl, + GetCurrentEditor().documentCharacterSet + ).pathQueryRef; + let urlPath = Services.io.newURI( + inputUrl, + GetCurrentEditor().documentCharacterSet + ).pathQueryRef; + + // We only return "urlPath", so we can convert the entire docPath for + // case-insensitive comparisons. + var doCaseInsensitive = docScheme == "file" && AppConstants.platform == "win"; + if (doCaseInsensitive) { + docPath = docPath.toLowerCase(); + } + + // Get document filename before we start chopping up the docPath + var docFilename = GetFilename(docPath); + + // Both url and doc paths now begin with "/" + // Look for shared dirs starting after that + urlPath = urlPath.slice(1); + docPath = docPath.slice(1); + + var firstDirTest = true; + var nextDocSlash = 0; + var done = false; + + // Remove all matching subdirs common to both doc and input urls + do { + nextDocSlash = docPath.indexOf("/"); + var nextUrlSlash = urlPath.indexOf("/"); + + if (nextUrlSlash == -1) { + // We're done matching and all dirs in url + // what's left is the filename + done = true; + + // Remove filename for named anchors in the same file + if (nextDocSlash == -1 && docFilename) { + var anchorIndex = urlPath.indexOf("#"); + if (anchorIndex > 0) { + var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath; + + if (urlFilename.startsWith(docFilename)) { + urlPath = urlPath.slice(anchorIndex); + } + } + } + } else if (nextDocSlash >= 0) { + // Test for matching subdir + var docDir = docPath.slice(0, nextDocSlash); + var urlDir = urlPath.slice(0, nextUrlSlash); + if (doCaseInsensitive) { + urlDir = urlDir.toLowerCase(); + } + + if (urlDir == docDir) { + // Remove matching dir+"/" from each path + // and continue to next dir. + docPath = docPath.slice(nextDocSlash + 1); + urlPath = urlPath.slice(nextUrlSlash + 1); + } else { + // No match, we're done. + done = true; + + // Be sure we are on the same local drive or volume + // (the first "dir" in the path) because we can't + // relativize to different drives/volumes. + // UNIX doesn't have volumes, so we must not do this else + // the first directory will be misinterpreted as a volume name. + if ( + firstDirTest && + docScheme == "file" && + AppConstants.platform != "unix" + ) { + return inputUrl; + } + } + } else { + // No more doc dirs left, we're done + done = true; + } + + firstDirTest = false; + } while (!done); + + // Add "../" for each dir left in docPath + while (nextDocSlash > 0) { + urlPath = "../" + urlPath; + nextDocSlash = docPath.indexOf("/", nextDocSlash + 1); + } + return urlPath; +} + +function MakeAbsoluteUrl(url) { + let resultUrl = TrimString(url); + if (!resultUrl) { + return resultUrl; + } + + // Check if URL is already absolute, i.e., it has a scheme + let urlScheme = GetScheme(resultUrl); + + if (urlScheme) { + return resultUrl; + } + + let docUrl = GetDocumentBaseUrl(); + let docScheme = GetScheme(docUrl); + + // Can't relativize if no doc scheme (page hasn't been saved) + if (!docScheme) { + return resultUrl; + } + + // Make a URI object to use its "resolve" method + let absoluteUrl = resultUrl; + let docUri = Services.io.newURI( + docUrl, + GetCurrentEditor().documentCharacterSet + ); + + try { + absoluteUrl = docUri.resolve(resultUrl); + // This is deprecated and buggy! + // If used, we must make it a path for the parent directory (remove filename) + // absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl); + } catch (e) {} + + return absoluteUrl; +} + +// Get the HREF of the page's <base> tag or the document location +// returns empty string if no base href and document hasn't been saved yet +function GetDocumentBaseUrl() { + try { + var docUrl; + + // if document supplies a <base> tag, use that URL instead + let base = GetCurrentEditor().document.querySelector("base"); + if (base) { + docUrl = base.getAttribute("href"); + } + if (!docUrl) { + docUrl = GetDocumentUrl(); + } + + if (!IsUrlAboutBlank(docUrl)) { + return docUrl; + } + } catch (e) {} + return ""; +} + +function GetDocumentUrl() { + try { + return GetCurrentEditor().document.URL; + } catch (e) {} + return ""; +} + +// Extract the scheme (e.g., 'file', 'http') from a URL string +function GetScheme(urlspec) { + var resultUrl = TrimString(urlspec); + // Unsaved document URL has no acceptable scheme yet + if (!resultUrl || IsUrlAboutBlank(resultUrl)) { + return ""; + } + + var scheme = ""; + try { + // This fails if there's no scheme + scheme = Services.io.extractScheme(resultUrl); + } catch (e) {} + + return scheme ? scheme.toLowerCase() : ""; +} + +function GetHost(urlspec) { + if (!urlspec) { + return ""; + } + + var host = ""; + try { + host = Services.io.newURI(urlspec).host; + } catch (e) {} + + return host; +} + +function GetUsername(urlspec) { + if (!urlspec) { + return ""; + } + + var username = ""; + try { + username = Services.io.newURI(urlspec).username; + } catch (e) {} + + return username; +} + +function GetFilename(urlspec) { + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return ""; + } + + var filename; + + try { + let uri = Services.io.newURI(urlspec); + if (uri) { + let url = uri.QueryInterface(Ci.nsIURL); + if (url) { + filename = url.fileName; + } + } + } catch (e) {} + + return filename ? filename : ""; +} + +// Return the url without username and password +// Optional output objects return extracted username and password strings +// This uses just string routines via nsIIOServices +function StripUsernamePassword(urlspec, usernameObj, passwordObj) { + urlspec = TrimString(urlspec); + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return urlspec; + } + + if (usernameObj) { + usernameObj.value = ""; + } + if (passwordObj) { + passwordObj.value = ""; + } + + // "@" must exist else we will never detect username or password + var atIndex = urlspec.indexOf("@"); + if (atIndex > 0) { + try { + let uri = Services.io.newURI(urlspec); + let username = uri.username; + let password = uri.password; + + if (usernameObj && username) { + usernameObj.value = username; + } + if (passwordObj && password) { + passwordObj.value = password; + } + if (username) { + let usernameStart = urlspec.indexOf(username); + if (usernameStart != -1) { + return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex + 1); + } + } + } catch (e) {} + } + return urlspec; +} + +function StripPassword(urlspec, passwordObj) { + urlspec = TrimString(urlspec); + if (!urlspec || IsUrlAboutBlank(urlspec)) { + return urlspec; + } + + if (passwordObj) { + passwordObj.value = ""; + } + + // "@" must exist else we will never detect password + var atIndex = urlspec.indexOf("@"); + if (atIndex > 0) { + try { + let password = Services.io.newURI(urlspec).password; + + if (passwordObj && password) { + passwordObj.value = password; + } + if (password) { + // Find last ":" before "@" + let colon = urlspec.lastIndexOf(":", atIndex); + if (colon != -1) { + // Include the "@" + return urlspec.slice(0, colon) + urlspec.slice(atIndex); + } + } + } catch (e) {} + } + return urlspec; +} + +// Version to use when you have an nsIURI object +function StripUsernamePasswordFromURI(uri) { + var urlspec = ""; + if (uri) { + try { + urlspec = uri.spec; + var userPass = uri.userPass; + if (userPass) { + let start = urlspec.indexOf(userPass); + urlspec = + urlspec.slice(0, start) + urlspec.slice(start + userPass.length + 1); + } + } catch (e) {} + } + return urlspec; +} + +function InsertUsernameIntoUrl(urlspec, username) { + if (!urlspec || !username) { + return urlspec; + } + + try { + let URI = Services.io.newURI( + urlspec, + GetCurrentEditor().documentCharacterSet + ); + URI.username = username; + return URI.spec; + } catch (e) {} + + return urlspec; +} + +function ConvertRGBColorIntoHEXColor(color) { + if (/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color)) { + var r = Number(RegExp.$1).toString(16); + if (r.length == 1) { + r = "0" + r; + } + var g = Number(RegExp.$2).toString(16); + if (g.length == 1) { + g = "0" + g; + } + var b = Number(RegExp.$3).toString(16); + if (b.length == 1) { + b = "0" + b; + } + return "#" + r + g + b; + } + + return color; +} + +/** *********** CSS */ + +function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName) { + var value; + if (Services.prefs.getBoolPref("editor.use_css") && IsHTMLEditor()) { + value = element.style.getPropertyValue(cssPropertyName); + } + + if (!value) { + value = element.getAttribute(attrName); + } + + if (!value) { + return ""; + } + + return value; +} + +/** *********** Miscellaneous */ +// Clone simple JS objects +function Clone(obj) { + var clone = {}; + for (var i in obj) { + if (typeof obj[i] == "object") { + clone[i] = Clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + return clone; +} + +/** + * Utility functions to handle shortended data: URLs in EdColorProps.js and EdImageOverlay.js. + */ + +/** + * Is the passed in image URI a shortened data URI? + * + * @returns {bool} + */ +function isImageDataShortened(aImageData) { + return /^data:/i.test(aImageData) && aImageData.includes("…"); +} + +/** + * Event handler for Copy or Cut + * + * @param aEvent the event + */ +function onCopyOrCutShortened(aEvent) { + // Put the original data URI onto the clipboard in case the value + // is a shortened data URI. + let field = aEvent.target; + let startPos = field.selectionStart; + if (startPos == undefined) { + return; + } + let endPos = field.selectionEnd; + let selection = field.value.substring(startPos, endPos).trim(); + + // Test that a) the user selected the whole value, + // b) the value is a data URI, + // c) it contains the ellipsis we added. Otherwise it could be + // a new value that the user pasted in. + if (selection == field.value.trim() && isImageDataShortened(selection)) { + aEvent.clipboardData.setData("text/plain", field.fullDataURI); + if (aEvent.type == "cut") { + // We have to cut the selection manually. Since we tested that + // everything was selected, we can just reset the field. + field.value = ""; + } + aEvent.preventDefault(); + } +} + +/** + * Set up element showing an image URI with a shortened version. + * and add event handler for Copy or Cut. + * + * @param aImageData the data: URL of the image to be shortened. + * Note: Original stored in 'aDialogField.fullDataURI'. + * @param aDialogField The field of the dialog to contain the data. + * @returns {bool} URL was shortened? + */ +function shortenImageData(aImageData, aDialogField) { + let shortened = false; + aDialogField.value = aImageData.replace( + /^(data:.+;base64,)(.*)/i, + function (match, nonDataPart, dataPart) { + if (dataPart.length <= 35) { + return match; + } + + shortened = true; + aDialogField.addEventListener("copy", onCopyOrCutShortened); + aDialogField.addEventListener("cut", onCopyOrCutShortened); + aDialogField.fullDataURI = aImageData; + aDialogField.removeAttribute("tooltiptext"); + aDialogField.setAttribute("tooltip", "shortenedDataURI"); + return ( + nonDataPart + + dataPart.substring(0, 5) + + "…" + + dataPart.substring(dataPart.length - 30) + ); + } + ); + return shortened; +} + +/** + * Return full data URIs for a shortened element. + * + * @param aDialogField The field of the dialog containing the data. + */ +function restoredImageData(aDialogField) { + return aDialogField.fullDataURI; +} |