diff options
Diffstat (limited to 'comm/mail/components/compose/content/dialogs/EdConvertToTable.js')
-rw-r--r-- | comm/mail/components/compose/content/dialogs/EdConvertToTable.js | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/comm/mail/components/compose/content/dialogs/EdConvertToTable.js b/comm/mail/components/compose/content/dialogs/EdConvertToTable.js new file mode 100644 index 0000000000..e7f19cff67 --- /dev/null +++ b/comm/mail/components/compose/content/dialogs/EdConvertToTable.js @@ -0,0 +1,325 @@ +/* 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 ../editorUtilities.js */ +/* import-globals-from EdDialogCommon.js */ + +document.addEventListener("dialogaccept", onAccept); +document.addEventListener("dialogcancel", onCancel); + +var gIndex; +var gCommaIndex = "0"; +var gSpaceIndex = "1"; +var gOtherIndex = "2"; + +// dialog initialization code +function Startup() { + if (!GetCurrentEditor()) { + window.close(); + return; + } + + gDialog.sepRadioGroup = document.getElementById("SepRadioGroup"); + gDialog.sepCharacterInput = document.getElementById("SepCharacterInput"); + gDialog.deleteSepCharacter = document.getElementById("DeleteSepCharacter"); + gDialog.collapseSpaces = document.getElementById("CollapseSpaces"); + + // We persist the user's separator character + gDialog.sepCharacterInput.value = + gDialog.sepRadioGroup.getAttribute("character"); + + gIndex = gDialog.sepRadioGroup.getAttribute("index"); + + switch (gIndex) { + case gCommaIndex: + default: + gDialog.sepRadioGroup.selectedItem = document.getElementById("comma"); + break; + case gSpaceIndex: + gDialog.sepRadioGroup.selectedItem = document.getElementById("space"); + break; + case gOtherIndex: + gDialog.sepRadioGroup.selectedItem = document.getElementById("other"); + break; + } + + // Set initial enable state on character input and "collapse" checkbox + SelectCharacter(gIndex); + + SetWindowLocation(); +} + +function InputSepCharacter() { + var str = gDialog.sepCharacterInput.value; + + // Limit input to 1 character + if (str.length > 1) { + str = str.slice(0, 1); + } + + // We can never allow tag or entity delimiters for separator character + if (str == "<" || str == ">" || str == "&" || str == ";" || str == " ") { + str = ""; + } + + gDialog.sepCharacterInput.value = str; +} + +function SelectCharacter(radioGroupIndex) { + gIndex = radioGroupIndex; + SetElementEnabledById("SepCharacterInput", gIndex == gOtherIndex); + SetElementEnabledById("CollapseSpaces", gIndex == gSpaceIndex); +} + +/* eslint-disable complexity */ +function onAccept() { + var sepCharacter = ""; + switch (gIndex) { + case gCommaIndex: + sepCharacter = ","; + break; + case gSpaceIndex: + sepCharacter = " "; + break; + case gOtherIndex: + sepCharacter = gDialog.sepCharacterInput.value.slice(0, 1); + break; + } + + var editor = GetCurrentEditor(); + var str; + try { + str = editor.outputToString( + "text/html", + kOutputLFLineBreak | kOutputSelectionOnly + ); + } catch (e) {} + if (!str) { + SaveWindowLocation(); + return; + } + + // Replace nbsp with spaces: + str = str.replace(/\u00a0/g, " "); + + // Strip out </p> completely + str = str.replace(/\s*<\/p>\s*/g, ""); + + // Trim whitespace adjacent to <p> and <br> tags + // and replace <p> with <br> + // (which will be replaced with </tr> below) + str = str.replace(/\s*<p>\s*|\s*<br>\s*/g, "<br>"); + + // Trim leading <br>s + str = str.replace(/^(<br>)+/, ""); + + // Trim trailing <br>s + str = str.replace(/(<br>)+$/, ""); + + // Reduce multiple internal <br> to just 1 + // TODO: Maybe add a checkbox to let user decide + // str = str.replace(/(<br>)+/g, "<br>"); + + // Trim leading and trailing spaces + str = str.trim(); + + // Remove all tag contents so we don't replace + // separator character within tags + // Also converts lists to something useful + var stack = []; + var start; + var end; + var searchStart = 0; + var listSeparator = ""; + var listItemSeparator = ""; + var endList = false; + + do { + start = str.indexOf("<", searchStart); + + if (start >= 0) { + end = str.indexOf(">", start + 1); + if (end > start) { + let tagContent = str.slice(start + 1, end).trim(); + + if (/^ol|^ul|^dl/.test(tagContent)) { + // Replace list tag with <BR> to start new row + // at beginning of second or greater list tag + str = str.slice(0, start) + listSeparator + str.slice(end + 1); + if (listSeparator == "") { + listSeparator = "<br>"; + } + + // Reset for list item separation into cells + listItemSeparator = ""; + } else if (/^li|^dt|^dd/.test(tagContent)) { + // Start a new row if this is first item after the ending the last list + if (endList) { + listItemSeparator = "<br>"; + } + + // Start new cell at beginning of second or greater list items + str = str.slice(0, start) + listItemSeparator + str.slice(end + 1); + + if (endList || listItemSeparator == "") { + listItemSeparator = sepCharacter; + } + + endList = false; + } else { + // Find end tags + endList = /^\/ol|^\/ul|^\/dl/.test(tagContent); + if (endList || /^\/li|^\/dt|^\/dd/.test(tagContent)) { + // Strip out tag + str = str.slice(0, start) + str.slice(end + 1); + } else { + // Not a list-related tag: Store tag contents in an array + stack.push(tagContent); + + // Keep the "<" and ">" while removing from source string + start++; + str = str.slice(0, start) + str.slice(end); + } + } + } + searchStart = start + 1; + } + } while (start >= 0); + + // Replace separator characters with table cells + var replaceString; + if (gDialog.deleteSepCharacter.checked) { + replaceString = ""; + } else { + // Don't delete separator character, + // so include it at start of string to replace + replaceString = sepCharacter; + } + + replaceString += "<td>"; + + if (sepCharacter.length > 0) { + var tempStr = sepCharacter; + var regExpChars = ".!@#$%^&*-+[]{}()|\\/"; + if (regExpChars.includes(sepCharacter)) { + tempStr = "\\" + sepCharacter; + } + + if (gIndex == gSpaceIndex) { + // If checkbox is checked, + // one or more adjacent spaces are one separator + if (gDialog.collapseSpaces.checked) { + tempStr = "\\s+"; + } else { + tempStr = "\\s"; + } + } + var pattern = new RegExp(tempStr, "g"); + str = str.replace(pattern, replaceString); + } + + // Put back tag contents that we removed above + searchStart = 0; + var stackIndex = 0; + do { + start = str.indexOf("<", searchStart); + end = start + 1; + if (start >= 0 && str.charAt(end) == ">") { + // We really need a FIFO stack! + str = str.slice(0, end) + stack[stackIndex++] + str.slice(end); + } + searchStart = end; + } while (start >= 0); + + // End table row and start another for each br or p + str = str.replace(/\s*<br>\s*/g, "</tr>\n<tr><td>"); + + // Add the table tags and the opening and closing tr/td tags + // Default table attributes should be same as those used in nsHTMLEditor::CreateElementWithDefaults() + // (Default width="100%" is used in EdInsertTable.js) + str = + '<table border="1" width="100%" cellpadding="2" cellspacing="2">\n<tr><td>' + + str + + "</tr>\n</table>\n"; + + editor.beginTransaction(); + + // Delete the selection -- makes it easier to find where table will insert + var nodeBeforeTable = null; + var nodeAfterTable = null; + try { + editor.deleteSelection(editor.eNone, editor.eStrip); + + var anchorNodeBeforeInsert = editor.selection.anchorNode; + var offset = editor.selection.anchorOffset; + if (anchorNodeBeforeInsert.nodeType == Node.TEXT_NODE) { + // Text was split. Table should be right after the first or before + nodeBeforeTable = anchorNodeBeforeInsert.previousSibling; + nodeAfterTable = anchorNodeBeforeInsert; + } else { + // Table should be inserted right after node pointed to by selection + if (offset > 0) { + nodeBeforeTable = anchorNodeBeforeInsert.childNodes.item(offset - 1); + } + + nodeAfterTable = anchorNodeBeforeInsert.childNodes.item(offset); + } + + editor.insertHTML(str); + } catch (e) {} + + var table = null; + if (nodeAfterTable) { + var previous = nodeAfterTable.previousSibling; + if (previous && previous.nodeName.toLowerCase() == "table") { + table = previous; + } + } + if (!table && nodeBeforeTable) { + var next = nodeBeforeTable.nextSibling; + if (next && next.nodeName.toLowerCase() == "table") { + table = next; + } + } + + if (table) { + // Fixup table only if pref is set + var firstRow; + try { + if (Services.prefs.getBoolPref("editor.table.maintain_structure")) { + editor.normalizeTable(table); + } + + firstRow = editor.getFirstRow(table); + } catch (e) {} + + // Put caret in first cell + if (firstRow) { + var node2 = firstRow.firstChild; + do { + if ( + node2.nodeName.toLowerCase() == "td" || + node2.nodeName.toLowerCase() == "th" + ) { + try { + editor.selection.collapse(node2, 0); + } catch (e) {} + break; + } + node2 = node2.nextSibling; + } while (node2); + } + } + + editor.endTransaction(); + + // Save persisted attributes + gDialog.sepRadioGroup.setAttribute("index", gIndex); + if (gIndex == gOtherIndex) { + gDialog.sepRadioGroup.setAttribute("character", sepCharacter); + } + + SaveWindowLocation(); +} +/* eslint-enable complexity */ |