/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2019 Red Hat (www.redhat.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see
"; level++; } while (newLevel < level) { html += ""; level--; } html += usePreTag ? "
" : ""; while (line[skip] == ' ') { skip++; html += " "; } if (skip) line = line.substr(skip); html += (line[0] ? line.replace(//g, ">") : ""; } while (0 < level) { html += ""; level--; } return html; } EvoEditor.convertParagraphs = function(parent, blockquoteLevel, wrapWidth, canChangeQuoteParagraphs) { if (!parent) return; var ii; for (ii = 0; ii < parent.children.length; ii++) { var child = parent.children.item(ii); if (child.tagName == "DIV") { if (wrapWidth == -1 || (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && blockquoteLevel > 0)) { child.style.width = ""; EvoEditor.removeEmptyStyleAttribute(child); } else { child.style.width = wrapWidth + "ch"; child.removeAttribute("x-evo-width"); } if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && blockquoteLevel > 0) EvoEditor.quoteParagraph(child, blockquoteLevel, wrapWidth); } else if (child.tagName == "PRE") { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && blockquoteLevel > 0) { var prefixHtml = EvoEditor.getBlockquotePrefixHtml(blockquoteLevel); var lines, jj, text; text = child.innerText; if (text == "\n" || text == "\r\n") lines = [ "" ]; else lines = text.split("\n"); text = ""; for (jj = 0; jj < lines.length; jj++) { text += prefixHtml + lines[jj].replace(/\&/g, "&").replace(//g, ">"); if (!lines[jj]) text += "
"); html += usePreTag ? "" : "
"; if (jj + 1 < lines.length) text += "\n"; } if (!lines.length) text += prefixHtml; child.innerHTML = text; } else { EvoEditor.convertParagraphs(child, blockquoteLevel, wrapWidth, canChangeQuoteParagraphs); } } else if (child.tagName == "BLOCKQUOTE") { var innerWrapWidth = wrapWidth; if (innerWrapWidth > 0) { innerWrapWidth -= 2; // length of "> " if (innerWrapWidth < EvoConvert.MIN_PARAGRAPH_WIDTH) innerWrapWidth = EvoConvert.MIN_PARAGRAPH_WIDTH; } // replace blockquote content with pure plain text and then re-blockquote it // and do it only on the top level, not recursively (nested citations) if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && !blockquoteLevel) { child.innerHTML = EvoEditor.reBlockquotePlainText(EvoConvert.ToPlainText(child, -1), (child.firstElementChild && child.firstElementChild.tagName == "PRE" && ( !canChangeQuoteParagraphs || !EvoEditor.WRAP_QUOTED_TEXT_IN_REPLIES))); } EvoEditor.convertParagraphs(child, blockquoteLevel + 1, innerWrapWidth, canChangeQuoteParagraphs); } else if (child.tagName == "UL") { if (wrapWidth == -1) { child.style.width = ""; EvoEditor.removeEmptyStyleAttribute(child); } else { var innerWrapWidth = wrapWidth; innerWrapWidth -= 3; // length of " * " prefix if (innerWrapWidth < EvoConvert.MIN_PARAGRAPH_WIDTH) innerWrapWidth = EvoConvert.MIN_PARAGRAPH_WIDTH; child.style.width = innerWrapWidth + "ch"; } } else if (child.tagName == "OL") { if (wrapWidth == -1) { child.style.width = ""; child.style.paddingInlineStart = ""; EvoEditor.removeEmptyStyleAttribute(child); } else { var innerWrapWidth = wrapWidth, olNeedWidth; olNeedWidth = EvoConvert.GetOLMaxLetters(child.getAttribute("type"), child.children.length) + 2; // length of ". " suffix if (olNeedWidth < EvoConvert.MIN_OL_WIDTH) olNeedWidth = EvoConvert.MIN_OL_WIDTH; innerWrapWidth -= olNeedWidth; if (innerWrapWidth < EvoConvert.MIN_PARAGRAPH_WIDTH) innerWrapWidth = EvoConvert.MIN_PARAGRAPH_WIDTH; child.style.width = innerWrapWidth + "ch"; child.style.paddingInlineStart = olNeedWidth + "ch"; } } } } EvoEditor.SetNormalParagraphWidth = function(value) { if (EvoEditor.NORMAL_PARAGRAPH_WIDTH != value) { EvoEditor.NORMAL_PARAGRAPH_WIDTH = value; if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) EvoEditor.convertParagraphs(document.body, 0, EvoEditor.NORMAL_PARAGRAPH_WIDTH, false); } } EvoEditor.moveNodeContent = function(node, intoNode) { if (!node || !node.parentElement) return; var parent = node.parentElement; while (node.firstChild) { if (intoNode) { intoNode.append(node.firstChild); } else { parent.insertBefore(node.firstChild, node); } } } EvoEditor.convertTags = function() { var ii, list; for (ii = document.images.length - 1; ii >= 0; ii--) { var img = document.images[ii]; img.outerText = EvoConvert.ImgToText(img); } list = document.getElementsByTagName("A"); for (ii = list.length - 1; ii >= 0; ii--) { var anchor = list[ii]; EvoEditor.moveNodeContent(anchor); anchor.remove(); } list = document.getElementsByTagName("TABLE"); for (ii = list.length - 1; ii >= 0; ii--) { var table = list[ii], textNode; textNode = document.createTextNode(table.innerText); table.parentElement.insertBefore(textNode, table); table.remove(); } list = document.getElementsByTagName("BLOCKQUOTE"); for (ii = list.length - 1; ii >= 0; ii--) { var blockquoteNode = list[ii]; blockquoteNode.removeAttribute("class"); blockquoteNode.removeAttribute("style"); } var node = document.body.firstChild, next; while (node) { var removeNode = false; if (node.nodeType == node.ELEMENT_NODE) { if (node.tagName != "DIV" && node.tagName != "PRE" && node.tagName != "BLOCKQUOTE" && node.tagName != "UL" && node.tagName != "OL" && node.tagName != "LI" && node.tagName != "BR") { removeNode = true; // convert P into DIV if (node.tagName == "P") { var div = document.createElement("DIV"); EvoEditor.moveNodeContent(node, div); node.parentElement.insertBefore(div, node.nextSibling); } else { EvoEditor.moveNodeContent(node); } } } // skip the signature wrapper if (!removeNode && node.tagName == "DIV" && node.className == "-x-evo-signature-wrapper") next = node.nextSibling; else next = EvoEditor.getNextNodeInHierarchy(node, document.body); if (removeNode) node.remove(); node = next; } document.body.normalize(); } EvoEditor.removeQuoteMarks = function(element) { var ii, list; if (!element) element = document; list = element.querySelectorAll("SPAN.-x-evo-quoted"); for (ii = list.length - 1; ii >= 0; ii--) { var node = list[ii]; node.remove(); } list = element.querySelectorAll("BR.-x-evo-wrap-br"); for (ii = list.length - 1; ii >= 0; ii--) { var node = list[ii]; node.insertAdjacentText("beforebegin", " "); node.remove(); } if (element === document) document.body.normalize(); else element.normalize(); } EvoEditor.cleanupForPlainText = function() { if (!document.body) return; // remove all body attributes, to not influence the Plain Text mode var ii; for (ii = document.body.attributes.length - 1; ii >= 0; ii--) { document.body.removeAttribute(document.body.attributes[ii].nodeName); } // style sheets for (ii = document.styleSheets.length - 1; ii >= 0; ii--) { if (document.styleSheets[ii].ownerNode) document.styleSheets[ii].ownerNode.remove(); } } EvoEditor.SetMode = function(mode) { if (EvoEditor.mode != mode) { var opType = "setMode::" + (mode == EvoEditor.MODE_PLAIN_TEXT ? "PlainText" : "HTML"), record; record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_DOCUMENT, opType, null, null); if (record) { record.modeBefore = EvoEditor.mode; record.modeAfter = mode; record.apply = function(record, isUndo) { var useMode = isUndo ? record.modeBefore : record.modeAfter; if (EvoEditor.mode != useMode) { EvoEditor.mode = useMode; } } } EvoUndoRedo.Disable(); try { EvoEditor.mode = mode; EvoEditor.removeQuoteMarks(null); if (mode == EvoEditor.MODE_PLAIN_TEXT) { EvoEditor.convertTags(); EvoEditor.convertParagraphs(document.body, 0, EvoEditor.NORMAL_PARAGRAPH_WIDTH, false); EvoEditor.cleanupForPlainText(); } else { EvoEditor.convertParagraphs(document.body, 0, -1, false); } } finally { EvoUndoRedo.Enable(); EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_DOCUMENT, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_YES); } } } EvoEditor.applyFontReset = function(record, isUndo) { if (record.changes) { var ii; for (ii = 0; ii < record.changes.length; ii++) { var change = record.changes[isUndo ? (record.changes.length - ii - 1) : ii]; var parent = EvoSelection.FindElementByPath(document.body, change.parentPath); if (!parent) { throw "EvoEditor.applyFontReset: Cannot find node at path " + change.path; } parent.innerHTML = isUndo ? change.htmlBefore : change.htmlAfter; } } } EvoEditor.replaceInheritFonts = function(undoRedoRecord, selectionUpdater, nodes) { var ii; if (!nodes) nodes = document.querySelectorAll("FONT[face=inherit]"); for (ii = nodes.length - 1; ii >= 0; ii--) { var node = nodes.item(ii); if (!node || (!undoRedoRecord && !document.getSelection().containsNode(node, true))) continue; var parent, change = null; parent = node.parentElement; if (undoRedoRecord) { if (!undoRedoRecord.changes) undoRedoRecord.changes = []; change = { parentPath : EvoSelection.GetChildPath(document.body, parent), htmlBefore : parent.innerHTML, htmlAfter : "" }; undoRedoRecord.changes[undoRedoRecord.changes.length] = change; } if (node.attributes.length == 1) { var child; while (node.firstChild) { var child = node.firstChild; selectionUpdater.beforeRemove(child); parent.insertBefore(child, node); selectionUpdater.afterRemove(child); } node.remove(); } else { node.removeAttribute("face"); } if (change) change.htmlAfter = parent.innerHTML; } if (undoRedoRecord && undoRedoRecord.changes) undoRedoRecord.apply = EvoEditor.applyFontReset; } EvoEditor.maybeReplaceInheritFonts = function() { var nodes = document.querySelectorAll("FONT[face=inherit]"); if (nodes.length <= 0) return; var record, selectionUpdater; selectionUpdater = EvoSelection.CreateUpdaterObject(); record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "UnsetFontName", null, null, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { EvoEditor.replaceInheritFonts(record, selectionUpdater, nodes); selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "UnsetFontName"); if (record) EvoUndoRedo.GroupTopRecords(2); } } EvoEditor.SetFontName = function(name) { if (!name || name == "") name = "inherit"; var record, selectionUpdater = EvoSelection.CreateUpdaterObject(), bodyFontFamily; // to workaround https://bugs.webkit.org/show_bug.cgi?id=204622 bodyFontFamily = document.body.style.fontFamily; record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "SetFontName"); try { if (!document.getSelection().isCollapsed && bodyFontFamily) document.body.style.fontFamily = ""; document.execCommand("FontName", false, name); if (document.getSelection().isCollapsed) { if (name == "inherit") EvoEditor.checkInheritFontsOnChange = true; /* Format change on collapsed selection is not applied immediately */ if (record) record.ignore = true; } else if (name == "inherit") { var subrecord; subrecord = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetFontName", null, null, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { EvoEditor.replaceInheritFonts(subrecord, selectionUpdater); selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetFontName"); } } } finally { if (bodyFontFamily && document.body.style.fontFamily != bodyFontFamily) document.body.style.fontFamily = bodyFontFamily; EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "SetFontName"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); EvoEditor.removeEmptyStyleAttribute(document.body); } } EvoEditor.convertHtmlToSend = function() { var html, bgcolor, text, link, vlink; var unsetBgcolor = false, unsetText = false, unsetLink = false, unsetVlink = false; var themeCss, inheritThemeColors = EvoEditor.inheritThemeColors; var ii, styles, styleNode = null, topSignatureSpacers, elems; themeCss = EvoEditor.UpdateThemeStyleSheet(null); bgcolor = document.documentElement.getAttribute("x-evo-bgcolor"); text = document.documentElement.getAttribute("x-evo-text"); link = document.documentElement.getAttribute("x-evo-link"); vlink = document.documentElement.getAttribute("x-evo-vlink"); document.documentElement.removeAttribute("x-evo-bgcolor"); document.documentElement.removeAttribute("x-evo-text"); document.documentElement.removeAttribute("x-evo-link"); document.documentElement.removeAttribute("x-evo-vlink"); topSignatureSpacers = document.querySelectorAll(".-x-evo-top-signature-spacer"); for (ii = topSignatureSpacers.length - 1; ii >= 0; ii--) { topSignatureSpacers[ii].removeAttribute("class"); } if (inheritThemeColors) { if (bgcolor && !document.body.getAttribute("bgcolor")) { document.body.setAttribute("bgcolor", bgcolor); unsetBgcolor = true; } if (text && !document.body.getAttribute("text")) { document.body.setAttribute("text", text); unsetText = true; } if (link && !document.body.getAttribute("link")) { document.body.setAttribute("link", link); unsetLink = true; } if (vlink && !document.body.getAttribute("vlink")) { document.body.setAttribute("vlink", vlink); unsetVlink = true; } } styles = document.head.getElementsByTagName("style"); for (ii = 0; ii < styles.length; ii++) { if (styles[ii].id == "x-evo-body-fontname") { styleNode = styles[ii]; styleNode.id = ""; break; } } if (EvoEditor.mode == EvoEditor.MODE_HTML) { elems = document.getElementsByTagName("BLOCKQUOTE"); for (ii = 0; ii < elems.length; ii++) { elems[ii].setAttribute("style", EvoEditor.BLOCKQUOTE_STYLE); elems[ii].removeAttribute("spellcheck"); } } html = document.documentElement.outerHTML; if (EvoEditor.mode == EvoEditor.MODE_HTML) { elems = document.getElementsByTagName("BLOCKQUOTE"); for (ii = 0; ii < elems.length; ii++) { elems[ii].removeAttribute("style"); elems[ii].setAttribute("spellcheck", "false"); } } if (styleNode) styleNode.id = "x-evo-body-fontname"; if (bgcolor) document.documentElement.setAttribute("x-evo-bgcolor", bgcolor); if (text) document.documentElement.setAttribute("x-evo-text", text); if (link) document.documentElement.setAttribute("x-evo-link", link); if (vlink) document.documentElement.setAttribute("x-evo-vlink", vlink); if (inheritThemeColors) { if (unsetBgcolor) document.body.removeAttribute("bgcolor"); if (unsetText) document.body.removeAttribute("text"); if (unsetLink) document.body.removeAttribute("link"); if (unsetVlink) document.body.removeAttribute("vlink"); } for (ii = topSignatureSpacers.length - 1; ii >= 0; ii--) { var elem = topSignatureSpacers[ii]; if (elem.previousSibling && elem.previousSibling.tagName == "DIV" && elem.previousSibling.className == "-x-evo-signature-wrapper") { elem.className = "-x-evo-top-signature-spacer"; break; } } if (themeCss) EvoEditor.UpdateThemeStyleSheet(themeCss); return html; } EvoEditor.GetContent = function(flags, cid_uid_prefix) { var content_data = {}; if (!document.body) return content_data; var img_elems = [], data_names = [], bkg_elems = [], elems, ii, jj, currentElemsArray = null; var scrollX = window.scrollX, scrollY = window.scrollY; EvoUndoRedo.Disable(); try { currentElemsArray = EvoEditor.RemoveCurrentElementAttr(); // For safety, to not export with empty 'style' attributes; these do not need Undo elems = document.querySelectorAll("[style='']"); for (ii = 0; ii < elems.length; ii++) { EvoEditor.removeEmptyStyleAttribute(elems[ii]); } if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED) != 0) { var hidden_elems = []; try { elems = document.getElementsByClassName("-x-evo-signature-wrapper"); if (elems && elems.length) { for (ii = 0; ii < elems.length; ii++) { var elem = elems.item(ii); if (elem && !elem.hidden) { hidden_elems[hidden_elems.length] = elem; elem.hidden = true; } } } elems = document.getElementsByTagName("BLOCKQUOTE"); if (elems && elems.length) { for (ii = 0; ii < elems.length; ii++) { var elem = elems.item(ii); if (elem && !elem.hidden) { hidden_elems[hidden_elems.length] = elem; elem.hidden = true; } } } content_data["raw-body-stripped"] = document.body.innerText; } finally { for (ii = 0; ii < hidden_elems.length; ii++) { hidden_elems[ii].hidden = false; } } } // Do these before changing image sources if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_HTML) != 0) content_data["raw-body-html"] = document.body.innerHTML; if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0) content_data["raw-body-plain"] = document.body.innerText; if (EvoEditor.mode == EvoEditor.MODE_HTML && (flags & EvoEditor.E_CONTENT_EDITOR_GET_INLINE_IMAGES) != 0) { var images = []; for (ii = 0; ii < document.images.length; ii++) { var elem = document.images.item(ii); var src = (elem && elem.src) ? elem.src.toLowerCase() : ""; if (elem && ( src.startsWith("data:") || src.startsWith("file://") || src.startsWith("evo-file://"))) { for (jj = 0; jj < img_elems.length; jj++) { if (elem.src == img_elems[jj].orig_src) { img_elems[jj].subelems[img_elems[jj].subelems.length] = elem; elem.src = img_elems[jj].cid; break; } } if (jj >= img_elems.length) { var img_obj = { subelems : [ elem ], cid : "cid:" + cid_uid_prefix + "-" + img_elems.length, orig_src : elem.src }; if (elem.src.toLowerCase().startsWith("cid:")) img_obj.cid = elem.src; img_elems[img_elems.length] = img_obj; images[images.length] = { cid : img_obj.cid, src : elem.src }; elem.src = img_obj.cid; if (elem.hasAttribute("data-name")) images[images.length - 1].name = elem.getAttribute("data-name"); } } else if (elem && src.startsWith("cid:")) { images[images.length] = { cid : elem.src, src : elem.src }; } if (elem) { // just remove the attribute used by the old editor elem.removeAttribute("data-inline"); if (elem.hasAttribute("data-name")) { data_names[data_names.length] = { elem : elem, name : elem.getAttribute("data-name") }; elem.removeAttribute("data-name"); } } } var backgrounds = document.querySelectorAll("[background]"); for (ii = 0; ii < backgrounds.length; ii++) { var elem = backgrounds[ii]; var src = elem ? elem.getAttribute("background").toLowerCase() : ""; if (elem && ( src.startsWith("data:") || src.startsWith("file://") || src.startsWith("evo-file://"))) { var bkg = elem.getAttribute("background"); for (jj = 0; jj < bkg_elems.length; jj++) { if (bkg == bkg_elems[jj].orig_src) { bkg_elems[jj].subelems[bkg_elems[jj].subelems.length] = elem; elem.setAttribute("background", bkg_elems[jj].cid); break; } } if (jj >= bkg_elems.length) { var bkg_obj = { subelems : [ elem ], cid : "cid:" + cid_uid_prefix + "-" + bkg_elems.length, orig_src : bkg }; // re-read, because it could change if (elem.getAttribute("background").toLowerCase().startsWith("cid:")) bkg_obj.cid = elem.getAttribute("background"); bkg_elems[bkg_elems.length] = bkg_obj; images[images.length] = { cid : bkg_obj.cid, src : elem.getAttribute("background") }; elem.setAttribute("background", bkg_obj.cid); if (elem.hasAttribute("data-name")) images[images.length - 1].name = elem.getAttribute("data-name"); } } else if (elem && src.startsWith("cid:")) { images[images.length] = { cid : elem.getAttribute("background"), src : elem.getAttribute("background") }; } if (elem) { if (elem.hasAttribute("data-name")) { data_names[data_names.length] = { elem : elem, name : elem.getAttribute("data-name") }; elem.removeAttribute("data-name"); } } } if (images.length) content_data["images"] = images; } // Draft should have replaced images as well if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0) { document.head.setAttribute("x-evo-selection", EvoSelection.ToString(EvoSelection.Store(document))); try { document.body.setAttribute("data-evo-draft", ""); content_data["raw-draft"] = document.documentElement.outerHTML; } finally { document.head.removeAttribute("x-evo-selection"); document.body.removeAttribute("data-evo-draft"); } } if ((flags & EvoEditor.E_CONTENT_EDITOR_GET_TO_SEND_HTML) != 0) content_data["to-send-html"] = EvoEditor.convertHtmlToSend(); if ((flags & EvoEditor. E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) != 0) { content_data["to-send-plain"] = EvoConvert.ToPlainText(document.body, EvoEditor.NORMAL_PARAGRAPH_WIDTH); } } finally { try { for (ii = 0; ii < img_elems.length; ii++) { var img_obj = img_elems[ii]; for (jj = 0; jj < img_obj.subelems.length; jj++) { img_obj.subelems[jj].src = img_obj.orig_src; } } for (ii = 0; ii < data_names.length; ii++) { data_names[ii].elem.setAttribute("data-name", data_names[ii].name); } for (ii = 0; ii < bkg_elems.length; ii++) { var bkg_obj = bkg_elems[ii]; for (jj = 0; jj < bkg_obj.subelems.length; jj++) { bkg_obj.subelems[jj].setAttribute("background", bkg_obj.orig_src); } } EvoEditor.RestoreCurrentElementAttr(currentElemsArray); } finally { EvoUndoRedo.Enable(); } } // the above changes can cause change of the scroll offset, thus restore it window.scrollTo(scrollX, scrollY); return content_data; } EvoEditor.UpdateStyleSheet = function(id, css) { var styles, ii, res = null; styles = document.head.getElementsByTagName("style"); for (ii = 0; ii < styles.length; ii++) { if (styles[ii].id == id) { res = styles[ii].innerHTML; if (css) styles[ii].innerHTML = css; else document.head.removeChild(styles[ii]); return res; } } if (css) { var style; style = document.createElement("STYLE"); style.id = id; style.innerHTML = css; document.head.append(style); } return res; } EvoEditor.UpdateThemeStyleSheet = function(css) { return EvoEditor.UpdateStyleSheet("x-evo-theme-sheet", css); } EvoEditor.findSmileys = function(text, unicodeSmileys) { /* Based on original use_pictograms() from GtkHTML */ var emoticons_chars = [ /* 0 */ "D", "O", ")", "(", "|", "/", "P", "Q", "*", "!", /* 10 */ "S", null, ":", "-", null, ":", null, ":", "-", null, /* 20 */ ":", null, ":", ";", "=", "-", "\"", null, ":", ";", /* 30 */ "B", "\"", "|", null, ":", "-", "'", null, ":", "X", /* 40 */ null, ":", null, ":", "-", null, ":", null, ":", "-", /* 50 */ null, ":", null, ":", "-", null, ":", null, ":", "-", /* 60 */ null, ":", null, ":", null, ":", "-", null, ":", null, /* 70 */ ":", "-", null, ":", null, ":", "-", null, ":", null ]; var emoticons_states = [ /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70, /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0, /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20, /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2, /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51, /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61, /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0, /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 ]; var emoticons_icon_names = [ "face-angel", "face-angry", "face-cool", "face-crying", "face-devilish", "face-embarrassed", "face-kiss", "face-laugh", /* not used */ "face-monkey", /* not used */ "face-plain", "face-raspberry", "face-sad", "face-sick", "face-smile", "face-smile-big", "face-smirk", "face-surprise", "face-tired", "face-uncertain", "face-wink", "face-worried" ]; var res = null, pos, state, start, uc; start = text.length - 1; if (start < 1) return res; pos = start; while (pos >= 0) { state = 0; while (pos >= 0) { uc = text[pos]; var relative = 0; while (emoticons_chars[state + relative] != null) { if (emoticons_chars[state + relative] == uc) { break; } relative++; } state = emoticons_states[state + relative]; /* 0 .. not found, -n .. found n-th */ if (state <= 0) break; pos--; } /* Special case needed to recognize angel and devilish. */ if (pos > 0 && state == -14) { uc = text[pos - 1]; if (uc == 'O') { state = -1; pos--; } else if (uc == '>') { state = -5; pos--; } } if (state < 0) { if (pos > 0) { uc = text[pos - 1]; if (uc != ' ') { return res; } } var obj = EvoEditor.lookupEmoticon(emoticons_icon_names[- state - 1], unicodeSmileys); if (obj) { obj.start = pos; obj.end = start + 1; if (!res) res = []; res[res.length] = obj; } pos--; start = pos; } else { break; } } return res; } EvoEditor.maybeUpdateParagraphWidth = function(topNode) { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { var node = topNode, citeLevel = 0; while (node && node.tagName != "BODY") { if (node.tagName == "BLOCKQUOTE") citeLevel++; node = node.parentElement; } if (citeLevel * 2 < EvoEditor.NORMAL_PARAGRAPH_WIDTH) { topNode.style.width = (EvoEditor.NORMAL_PARAGRAPH_WIDTH - citeLevel * 2) + "ch"; } } } EvoEditor.splitAtChild = function(parent, childNode) { var newNode, node, next, ii; newNode = parent.ownerDocument.createElement(parent.tagName); for (ii = 0; ii < parent.attributes.length; ii++) { newNode.setAttribute(parent.attributes[ii].nodeName, parent.attributes[ii].nodeValue); } node = childNode; while (node) { next = node.nextSibling; newNode.appendChild(node); node = next; } parent.parentElement.insertBefore(newNode, parent.nextSibling); return newNode; } EvoEditor.hasElementWithTagNameAsParent = function(node, tagName) { if (!node) return false; for (node = node.parentElement; node; node = node.parentElement) { if (node.tagName == tagName) return true; } return false; } EvoEditor.requoteNodeParagraph = function(node) { while (node && node.tagName != "BODY" && !EvoEditor.IsBlockNode(node)) { node = node.parentElement; } if (!node || node.tagName == "BODY") return null; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "requote", node, node, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var blockquoteLevel = EvoEditor.getBlockquoteLevel(node); EvoEditor.quoteParagraph(node, blockquoteLevel, EvoEditor.NORMAL_PARAGRAPH_WIDTH - (2 * blockquoteLevel)); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "requote"); } return node; } EvoEditor.replaceMatchWithNode = function(opType, node, match, newNode, canEmit, withUndo) { if (withUndo) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType, node.parentElement, node.parentElement, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); } try { var selection = document.getSelection(); var offset = selection.anchorOffset, updateSelection = selection.anchorNode === node, newAnchorNode; node.splitText(match.end); newAnchorNode = node.nextSibling; node.splitText(match.start); node = node.nextSibling; node.parentElement.insertBefore(newNode, node); if (newNode.tagName == "A") newNode.appendChild(node); else node.remove(); if (updateSelection && newAnchorNode && offset - match.end >= 0) selection.setPosition(newAnchorNode, offset - match.end); } finally { if (withUndo) { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType); if (canEmit) { EvoUndoRedo.GroupTopRecords(2); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } return node; } EvoEditor.linkifyText = function(anchorNode, withUndo) { if (!anchorNode) return false; var text = anchorNode.nodeValue, tmpNode; if (!text) return false; for (tmpNode = anchorNode; tmpNode && tmpNode.tagName != "BODY"; tmpNode = tmpNode.parentElement) { if (tmpNode.tagName == "A") { return false; } } var parts, ii; parts = EvoEditor.splitTextWithLinks(text); if (!parts) return false; if (withUndo) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink", anchorNode.parentElement, anchorNode.parentElement, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); } try { var selection = document.getSelection(), matchEnd = 0, insBefore, parent; var offset = selection.anchorOffset, updateSelection = selection.anchorNode === anchorNode, newAnchorNode = null; insBefore = anchorNode; parent = anchorNode.parentElement; for (ii = 0; ii < parts.length; ii++) { var part = parts[ii], node, isLast = ii + 1 >= parts.length; if (part.href) { node = document.createElement("A"); node.href = part.href; node.innerText = part.text; } else if (isLast) { node = null; // it can be a space, which cannot be added after the element, thus workaround it this way newAnchorNode = anchorNode.splitText(matchEnd); } else { node = document.createTextNode(part.text); } if (node) parent.insertBefore(node, insBefore); if (!isLast) { matchEnd += part.text.length; } else if (node) { newAnchorNode = node; } } if (anchorNode) anchorNode.remove(); if (updateSelection && newAnchorNode && offset - matchEnd >= 0) selection.setPosition(newAnchorNode, offset - matchEnd); } finally { if (withUndo) { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "magicLink"); EvoUndoRedo.GroupTopRecords(2); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } return true; } EvoEditor.maybeRemoveQuotationMark = function(node) { if (!node || node.nodeType != node.ELEMENT_NODE || node.tagName != "SPAN" || node.className != "-x-evo-quoted") return false; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "removeQuotationMark", node, node, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML | EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { node.remove(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "removeQuotationMark"); } return true; } EvoEditor.removeEmptyElements = function(tagName) { var nodes, node, ii, didRemove = 0; nodes = document.getElementsByTagName(tagName); for (ii = nodes.length - 1; ii >= 0; ii--) { node = nodes[ii]; // more than one child element means it's not empty if (node.childElementCount > 1) continue; // the first element is not quotation mark if (node.firstElementChild && (node.firstElementChild.tagName != "SPAN" || node.firstElementChild.className != "-x-evo-quoted")) continue; // the text inside is not empty, possibly on either of the two sides of the quotation mark if ((node.firstChild && (node.firstChild.nodeType == node.TEXT_NODE && node.firstChild.nodeValue)) || (node.lastChild && !(node.lastChild === node.firstChild) && (node.lastChild.nodeType == node.TEXT_NODE && node.lastChild.nodeValue))) continue; // it's either completely empty or it contains only the quotation mark and nothing else didRemove++; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "removeEmptyElem::" + tagName, node.parentElement, node.parentElement, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML | EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { node.remove(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "removeEmptyElem::" + tagName); } } return didRemove; } EvoEditor.beforeInputCb = function(inputEvent) { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && inputEvent && ( inputEvent.inputType == "deleteContentForward" || inputEvent.inputType == "deleteContentBackward")) { var selection = document.getSelection(); // workaround WebKit bug https://bugs.webkit.org/show_bug.cgi?id=209605 if (selection.anchorNode && selection.anchorNode.nodeType == selection.anchorNode.ELEMENT_NODE && selection.isCollapsed && EvoEditor.IsBlockNode(selection.anchorNode) && selection.anchorNode.firstChild.tagName == "BR" && !selection.anchorNode.firstChild.nextSibling) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_EVENT, inputEvent.inputType, selection.anchorNode, selection.anchorNode, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML | EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { var next = selection.anchorNode.nextSibling; if (!next) next = selection.anchorNode.previousSibling; if (!next) next = selection.anchorNode.parentElement; selection.anchorNode.remove(); selection.setPosition(next, 0); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_EVENT, inputEvent.inputType); } inputEvent.stopImmediatePropagation(); inputEvent.stopPropagation(); inputEvent.preventDefault(); return; } } if (EvoUndoRedo.disabled || !inputEvent || inputEvent.inputType != "insertText" || !inputEvent.data || inputEvent.data.length != 1 || inputEvent.data == " " || inputEvent.data == "\t") return; var selection = document.getSelection(); // when writing at the end of the anchor, then write into the anchor, not out (WebKit writes out) if (!selection || !selection.isCollapsed || !selection.anchorNode || selection.anchorNode.nodeType != selection.anchorNode.TEXT_NODE || selection.anchorOffset != selection.anchorNode.nodeValue.length || !selection.anchorNode.parentElement || selection.anchorNode.parentElement.tagName != "A") return; var node = selection.anchorNode; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_EVENT, "insertText", selection.anchorNode, selection.anchorNode, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML | EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { node.nodeValue += inputEvent.data; selection.setPosition(node, node.nodeValue.length); if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) node.parentElement.href = node.nodeValue; } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_EVENT, "insertText"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } // it will add the text, if anything breaks before it gets here inputEvent.stopImmediatePropagation(); inputEvent.stopPropagation(); inputEvent.preventDefault(); } EvoEditor.AfterInputEvent = function(inputEvent, isWordDelim) { var isInsertParagraph = inputEvent.inputType == "insertParagraph"; var selection = document.getSelection(); if (isInsertParagraph && selection.isCollapsed && selection.anchorNode && selection.anchorNode.tagName == "BODY") { document.execCommand("insertHTML", false, EvoEditor.emptyParagraphAsHtml()); EvoUndoRedo.GroupTopRecords(2, "insertParagraph::withFormat"); return; } // make sure there's always a DIV in the body (like after 'select all' followed by 'delete') if (!document.body.childNodes.length || (document.body.childNodes.length == 1 && document.body.childNodes[0].tagName == "BR")) { document.execCommand("insertHTML", false, EvoEditor.emptyParagraphAsHtml()); EvoUndoRedo.GroupTopRecords(2, inputEvent.inputType + "::fillEmptyBody"); return; } if (isInsertParagraph && selection.isCollapsed && selection.anchorNode && selection.anchorNode.tagName == "SPAN" && selection.anchorNode.children.length == 1 && selection.anchorNode.firstElementChild.tagName == "BR" && selection.anchorNode.parentElement.tagName == "DIV") { // new paragraph in UL/OL creates:// thus avoid the , which is not expected in the EvoEditor var node = selection.anchorNode; while (node.firstChild) { node.parentElement.insertBefore(node.firstChild, node); } selection.setPosition(node.parentElement, 0); node.remove(); } if (isInsertParagraph && selection.isCollapsed && selection.anchorNode && selection.anchorNode.tagName == "DIV") { // for example when moving away from ul/ol, the newly created // paragraph can inherit styles from it, which is also negative text-indent selection.anchorNode.style.textIndent = ""; EvoEditor.removeEmptyStyleAttribute(selection.anchorNode); EvoEditor.maybeUpdateParagraphWidth(selection.anchorNode); // it can be inherited, which is not desired when the user edits the content of it if (selection.anchorNode.className == "-x-evo-top-signature-spacer") selection.anchorNode.removeAttribute("class"); } // inserting paragraph in BLOCKQUOTE creates a new BLOCKQUOTE withoutinside it if (isInsertParagraph && selection.isCollapsed && selection.anchorNode && (selection.anchorNode.tagName == "BLOCKQUOTE" || (selection.anchorNode.nodeType == selection.anchorNode.TEXT_NODE && selection.anchorNode.parentElement && selection.anchorNode.parentElement.tagName == "BLOCKQUOTE"))) { var blockquoteNode = selection.anchorNode; if (blockquoteNode.nodeType == blockquoteNode.TEXT_NODE) blockquoteNode = blockquoteNode.parentElement; if (!blockquoteNode.firstChild || !EvoEditor.IsBlockNode(blockquoteNode.firstChild)) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "blockquoteFix", blockquoteNode, blockquoteNode, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var divNode = document.createElement("DIV"); while (blockquoteNode.firstChild) { divNode.appendChild(blockquoteNode.firstChild); } blockquoteNode.appendChild(divNode); EvoEditor.maybeUpdateParagraphWidth(divNode); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "blockquoteFix"); EvoUndoRedo.GroupTopRecords(2, "insertParagraph::blockquoteFix"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } // special editing of blockquotes if (selection.isCollapsed && (inputEvent.inputType.startsWith("insert") || inputEvent.inputType.startsWith("delete"))) { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && inputEvent.inputType.startsWith("delete")) { var didRemove = 0; didRemove += EvoEditor.removeEmptyElements("DIV"); didRemove += EvoEditor.removeEmptyElements("PRE"); if (didRemove) EvoUndoRedo.GroupTopRecords(didRemove + 1, inputEvent.inputType + "::removeEmptyElems"); } if (EvoEditor.hasElementWithTagNameAsParent(selection.anchorNode, "BLOCKQUOTE") && !EvoEditor.hasElementWithTagNameAsParent(selection.anchorNode, "UL") && !EvoEditor.hasElementWithTagNameAsParent(selection.anchorNode, "OL") && !EvoEditor.hasElementWithTagNameAsParent(selection.anchorNode, "TABLE")) { // insertParagraph should split the blockquote into two if (isInsertParagraph) { var node = selection.anchorNode, childNode = node, parent, removeNode = null; for (parent = node.parentElement; parent && parent.tagName != "BODY"; parent = parent.parentElement) { if (parent.tagName == "BLOCKQUOTE") { childNode = parent; break; } } EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "blockquoteSplit", childNode, childNode, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { if (node.nodeType == node.ELEMENT_NODE && node.childNodes.length == 1 && node.firstChild.tagName == "BR") removeNode = node; else if (node.nodeType == node.ELEMENT_NODE && node.childNodes.length > 1 && node.firstChild.tagName == "BR") removeNode = node.firstChild; childNode = node; for (parent = node.parentElement; parent && parent.tagName != "BODY"; parent = parent.parentElement) { if (parent.nodeType == parent.ELEMENT_NODE) { childNode = EvoEditor.splitAtChild(parent, childNode); parent = childNode; } else { childNode = parent; } } if (parent) { var divNode = document.createElement("DIV"); divNode.appendChild(document.createElement("BR")); parent.insertBefore(divNode, childNode); document.getSelection().setPosition(divNode, 0); EvoEditor.maybeUpdateParagraphWidth(divNode); } while (removeNode && removeNode.tagName != "BODY") { node = removeNode.parentElement; node.removeChild(removeNode); if (node.childNodes.length) break; removeNode = node; } if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { node = document.getSelection().anchorNode; if (node && node.nextElementSibling) { var blockquoteLevel = (node.nextElementSibling.tagName == "BLOCKQUOTE" ? 1 : 0); EvoEditor.removeQuoteMarks(node.nextElementSibling); EvoEditor.convertParagraphs(node.nextElementSibling, blockquoteLevel, EvoEditor.NORMAL_PARAGRAPH_WIDTH - (blockquoteLevel * 2), false); } if (node && node.previousElementSibling) { var blockquoteLevel = (node.previousElementSibling.tagName == "BLOCKQUOTE" ? 1 : 0); EvoEditor.removeQuoteMarks(node.previousElementSibling); EvoEditor.convertParagraphs(node.previousElementSibling, blockquoteLevel, EvoEditor.NORMAL_PARAGRAPH_WIDTH - (blockquoteLevel * 2), false); } } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "blockquoteSplit"); var didRemove = 0; didRemove += EvoEditor.removeEmptyElements("DIV"); didRemove += EvoEditor.removeEmptyElements("PRE"); EvoUndoRedo.GroupTopRecords(2 + didRemove, "insertParagraph::blockquoteSplit"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } // insertLineBreak should re-quote text in the Plain Text mode } else if (inputEvent.inputType == "insertLineBreak") { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { var selNode = document.getSelection().anchorNode, node = selNode, parent; while (node && node.tagName != "BODY" && !EvoEditor.IsBlockNode(node)) { node = node.parentElement; } if (node && node.tagName != "BODY" && selNode.previousSibling && selNode.previousSibling.nodeValue == "\n") { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "requote", node, node, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var blockquoteLevel; // the "\n" is replaced with full paragraph selNode.parentElement.removeChild(selNode.previousSibling); parent = selNode.parentElement; var childNode = selNode; while (parent && parent.tagName != "BODY") { childNode = EvoEditor.splitAtChild(parent, childNode); if (childNode === node || EvoEditor.IsBlockNode(parent)) break; parent = childNode.parentElement; } blockquoteLevel = EvoEditor.getBlockquoteLevel(parent); EvoEditor.quoteParagraph(childNode, blockquoteLevel, EvoEditor.NORMAL_PARAGRAPH_WIDTH - (2 * blockquoteLevel)); document.getSelection().setPosition(childNode, 0); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "requote"); EvoUndoRedo.GroupTopRecords(2, "insertLineBreak::requote"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } // it's an insert or delete in the blockquote, which means to recalculate where quotation marks should be } else if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { var node = document.getSelection().anchorNode; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "requote::group"); try { var selection = EvoSelection.Store(document); EvoEditor.removeEmptyElements("DIV"); EvoEditor.removeEmptyElements("PRE"); node = EvoEditor.requoteNodeParagraph(node); if (node && inputEvent.inputType.startsWith("delete")) { if (node.nextSiblingElement) EvoEditor.requoteNodeParagraph(node.nextSiblingElement); if (node.previousSiblingElement) EvoEditor.requoteNodeParagraph(node.previousSiblingElement); } EvoSelection.Restore(document, selection); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "requote::group"); EvoUndoRedo.GroupTopRecords(2, inputEvent.inputType + "::requote"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } } if ((!isInsertParagraph && inputEvent.inputType != "insertText") || (!(EvoEditor.MAGIC_LINKS && (isWordDelim || isInsertParagraph)) && !EvoEditor.MAGIC_SMILEYS)) { return; } if (!selection.isCollapsed || !selection.anchorNode) return; var anchorNode = selection.anchorNode, parentElem; if (anchorNode.nodeType != anchorNode.ELEMENT_NODE) { parentElem = anchorNode.parentElement; if (!parentElem) return; } else { parentElem = anchorNode; } if (isInsertParagraph) { parentElem = parentElem.previousElementSibling; if (!parentElem) return; anchorNode = parentElem.lastChild; if (!anchorNode || anchorNode.nodeType != anchorNode.TEXT_NODE) return; } if (!anchorNode.nodeValue) return; var canLinks; canLinks = EvoEditor.MAGIC_LINKS && (isWordDelim || isInsertParagraph); var text = anchorNode.nodeValue, covered = false; if (canLinks) covered = EvoEditor.linkifyText(anchorNode, true); if (!covered && EvoEditor.MAGIC_SMILEYS) { var matches; // the replace call below replaces (0xA0) with regular space matches = EvoEditor.findSmileys(text.replace(/Â /g, " "), EvoEditor.UNICODE_SMILEYS); if (matches) { var ii, sz = matches.length, node, tmpElement = null; if (sz > 1) EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "magicSmiley"); try { // they are ordered from the end already for (ii = 0; ii < sz; ii++) { var match = matches[ii]; if (!match.imageUri || EvoEditor.UNICODE_SMILEYS || EvoEditor.mode != EvoEditor.MODE_HTML) { node = document.createTextNode(match.text); } else { if (!tmpElement) tmpElement = document.createElement("SPAN"); tmpElement.innerHTML = EvoEditor.createEmoticonHTML(match.text, match.imageUri, match.width, match.height); node = tmpElement.firstChild; } anchorNode = EvoEditor.replaceMatchWithNode("magicSmiley", anchorNode, match, node, sz == 1, true); } } finally { if (sz > 1) { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "magicSmiley"); EvoUndoRedo.GroupTopRecords(2); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } } } EvoEditor.getParentElement = function(tagName, fromNode, canClimbUp) { var node = fromNode; if (!node) node = document.getSelection().focusNode; if (!node) node = document.getSelection().anchorNode; while (node && node.nodeType != node.ELEMENT_NODE) { node = node.parentElement; } if (canClimbUp) { while (node && node.tagName != tagName) { node = node.parentElement; } } if (node && node.tagName == tagName) return node; return null; } EvoEditor.storePropertiesSelection = function() { EvoEditor.propertiesSelection = EvoSelection.Store(document); } EvoEditor.restorePropertiesSelection = function() { if (EvoEditor.propertiesSelection) { var selection = EvoEditor.propertiesSelection; EvoEditor.propertiesSelection = null; try { // Ignore any errors here EvoSelection.Restore(document, selection); } catch (exception) { } } } // returns an array with affected elements, which can be passed to EvoEditor.RestoreCurrentElementAttr() EvoEditor.RemoveCurrentElementAttr = function() { var nodes, ii, len, elems = []; nodes = document.querySelectorAll("[" + EvoEditor.CURRENT_ELEMENT_ATTR + "]"); len = nodes ? nodes.length : 0; for (ii = 0; ii < len; ii++) { var elem = nodes[len - ii - 1]; elems[elems.length] = elem; elem.removeAttribute(EvoEditor.CURRENT_ELEMENT_ATTR); } return elems; } EvoEditor.RestoreCurrentElementAttr = function(elemsArray) { if (elemsArray) { var ii; for (ii = 0; ii < elemsArray.length; ii++) { elemsArray[ii].setAttribute(EvoEditor.CURRENT_ELEMENT_ATTR, "1"); } } } EvoEditor.getCurrentElement = function() { return document.querySelector("[" + EvoEditor.CURRENT_ELEMENT_ATTR + "]"); } EvoEditor.setCurrentElement = function(element) { EvoEditor.RemoveCurrentElementAttr(); if (element) element.setAttribute(EvoEditor.CURRENT_ELEMENT_ATTR, "1"); } EvoEditor.OnDialogOpen = function(name) { EvoEditor.propertiesSelection = null; EvoEditor.RemoveCurrentElementAttr(); var node = null; if (name == "link" || name == "cell" || name == "page") { EvoEditor.storePropertiesSelection(); if (name == "cell") { var tdnode, thnode; tdnode = (EvoEditor.contextMenuNode && EvoEditor.contextMenuNode.tagName == "TD") ? EvoEditor.contextMenuNode : EvoEditor.getParentElement("TD", null, false); thnode = (EvoEditor.contextMenuNode && EvoEditor.contextMenuNode.tagName == "TH") ? EvoEditor.contextMenuNode : EvoEditor.getParentElement("TH", null, false); if (tdnode === EvoEditor.contextMenuNode) { node = tdnode; } else if (thnode === EvoEditor.contextMenuNode) { node = thnode; } else if (tdnode && thnode) { for (node = thnode; node; node = node.parentElement) { if (node === tdnode) { // TH is a child of TD node = thnode; break; } } if (!node) node = tdnode; } else { node = tdnode ? tdnode : thnode; } if (node) EvoEditor.setCurrentElement(node); } if (name == "cell" || name == "page") EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "Dialog::" + name); } else if (name == "hrule" || name == "image" || name == "table") { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "Dialog::" + name); if (name == "hrule") { node = (EvoEditor.contextMenuNode && EvoEditor.contextMenuNode.tagName == "HR") ? EvoEditor.contextMenuNode : EvoEditor.getParentElement("HR", null, false); } else if (name == "image") { node = (EvoEditor.contextMenuNode && EvoEditor.contextMenuNode.tagName == "IMG") ? EvoEditor.contextMenuNode : EvoEditor.getParentElement("IMG", null, false); } else if (name == "table") { node = (EvoEditor.contextMenuNode && EvoEditor.contextMenuNode.tagName == "TABLE") ? EvoEditor.contextMenuNode : EvoEditor.getParentElement("TABLE", null, true); } if (node) { EvoEditor.setCurrentElement(node); } else { if (name == "hrule") EvoEditor.InsertHTML("CreateHRule", "
"); else if (name == "image") EvoEditor.InsertHTML("CreateImage", ""); else if (name == "table") EvoEditor.InsertHTML("CreateTable", "
"); } } node = EvoEditor.getCurrentElement(); if (node && name != "table" && name != "cell" && name != "image") { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_EVENT, "Dialog::" + name + "::event", node, node, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); EvoUndoRedo.Disable(); } } EvoEditor.OnDialogClose = function(name) { if (name == "link" || name == "cell") EvoEditor.restorePropertiesSelection(); else EvoEditor.propertiesSelection = null; EvoEditor.contextMenuNode = null; var node = EvoEditor.getCurrentElement(); EvoEditor.RemoveCurrentElementAttr(); if (node && name != "table" && name != "cell" && name != "image") { EvoUndoRedo.Enable(); EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_EVENT, "Dialog::" + name + "::event"); } if (name == "hrule" || name == "image" || name == "table" || name == "cell" || name == "page") EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "Dialog::" + name); } EvoEditor.applySetAttribute = function(record, isUndo) { var element = EvoSelection.FindElementByPath(document.body, record.path); if (!element) throw "EvoEditor.applySetAttribute: Path not found"; var value; if (isUndo) value = record.beforeValue; else value = record.afterValue; if (value == null) element.removeAttribute(record.attrName); else element.setAttribute(record.attrName, value); } EvoEditor.setAttributeWithUndoRedo = function(opTypePrefix, element, name, value) { if (!element) return false; if ((value == null && !element.hasAttribute(name)) || (value != null && value == element.getAttribute(name))) return false; var record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opTypePrefix + "::" + name, element, element, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { if (record) { record.path = EvoSelection.GetChildPath(document.body, element); record.attrName = name; record.beforeValue = element.hasAttribute(name) ? element.getAttribute(name) : null; record.afterValue = value; record.apply = EvoEditor.applySetAttribute; } if (value == null) { element.removeAttribute(name); } else { element.setAttribute(name, value); } if (record && record.beforeValue == record.afterValue) { record.ignore = true; } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opTypePrefix + "::" + name); if (!EvoUndoRedo.IsRecording()) { EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } return true; } EvoEditor.addElementWithUndoRedo = function(opType, tagName, fillNodeFunc, parent, insertBefore, contentArray) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType, parent, parent, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var selectionUpdater = EvoSelection.CreateUpdaterObject(), node; node = document.createElement(tagName); if (fillNodeFunc) fillNodeFunc(node); parent.insertBefore(node, insertBefore); if (contentArray) { var ii; for (ii = 0; ii < contentArray.length; ii++) { node.append(contentArray[ii]); } } selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.removeElementWithUndoRedo = function(opType, element) { if (element) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType, element.parentElement, element.parentElement, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var selectionUpdater = EvoSelection.CreateUpdaterObject(), firstChild; firstChild = element.firstChild; while (element.firstChild) { element.parentElement.insertBefore(element.firstChild, element); } selectionUpdater.beforeRemove(element); element.remove(); selectionUpdater.afterRemove(firstChild); selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } } // 'value' can be 'null', to remove the attribute EvoEditor.DialogUtilsSetAttribute = function(selector, name, value) { var element; if (selector) element = document.querySelector(selector); else element = EvoEditor.getCurrentElement(); if (element) { EvoEditor.setAttributeWithUndoRedo("DlgUtilsSetAttribute", element, name, value); } } EvoEditor.DialogUtilsGetAttribute = function(selector, name) { var element; if (selector) element = document.querySelector(selector); else element = EvoEditor.getCurrentElement(); if (element && element.hasAttribute(name)) return element.getAttribute(name); return null; } EvoEditor.DialogUtilsHasAttribute = function(name) { var element = EvoEditor.getCurrentElement(); return element && element.hasAttribute(name); } EvoEditor.LinkGetProperties = function() { var res = null, anchor = EvoEditor.getParentElement("A", null, false); if (anchor) { res = []; res["href"] = anchor.href; res["text"] = anchor.innerText; } else if (!document.getSelection().isCollapsed && document.getSelection().rangeCount > 0) { var range; range = document.getSelection().getRangeAt(0); if (range) { res = []; res["text"] = range.toString(); } } return res; } EvoEditor.LinkSetProperties = function(href, text) { // The properties dialog can discard selection, thus restore it before doing changes EvoEditor.restorePropertiesSelection(); var anchor = EvoEditor.getParentElement("A", null, false); if (anchor && (anchor.href != href || anchor.innerText != text)) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetLinkValues", anchor, anchor, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { if (anchor.href != href) anchor.href = href; if (anchor.innerText != text) { var selection = EvoSelection.Store(document); anchor.innerText = text; EvoSelection.Restore(document, selection); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "SetLinkValues"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } else if (!anchor && href != "" && text != "") { text = text.replace(/\&/g, "&").replace(//g, ">"); href = href.replace(/\&/g, "&").replace(/\"/g, """); EvoEditor.InsertHTML("CreateLink", "" + text + ""); } } EvoEditor.Unlink = function() { // The properties dialog can discard selection, thus restore it before doing changes EvoEditor.restorePropertiesSelection(); var anchor = EvoEditor.getParentElement("A", null, false); EvoEditor.removeElementWithUndoRedo("Unlink", anchor); } EvoEditor.ReplaceImageSrc = function(selector, uri) { if (!selector) selector = "#x-evo-dialog-current-element"; var element = document.querySelector(selector); if (element) { if (uri) { var attrName; if (element.tagName == "IMG") attrName = "src"; else attrName = "background"; EvoEditor.setAttributeWithUndoRedo("ReplaceImageSrc", element, attrName, uri); } else { if (element.tagName == "IMG") { EvoEditor.removeElementWithUndoRedo("ReplaceImageSrc", element); } else { EvoEditor.setAttributeWithUndoRedo("ReplaceImageSrc", element, "background", null); } } } } EvoEditor.DialogUtilsSetImageUrl = function(href) { var element = EvoEditor.getCurrentElement(); if (element && element.tagName == "IMG") { var anchor = EvoEditor.getParentElement("A", element, true); if (anchor) { if (href && anchor.href != href) { EvoEditor.setAttributeWithUndoRedo("DialogUtilsSetImageUrl", anchor, "href", href); } else if (!href) { EvoEditor.removeElementWithUndoRedo("DialogUtilsSetImageUrl::unset", element); } } else if (href) { var fillHref = function(node) { node.href = href; }; EvoEditor.addElementWithUndoRedo("DialogUtilsSetImageUrl", "A", fillHref, element.parentElement, element, [ element ]); } } } EvoEditor.DialogUtilsGetImageUrl = function() { var element = EvoEditor.getCurrentElement(), res = null; if (element && element.tagName == "IMG") { var anchor = EvoEditor.getParentElement("A", element, true); if (anchor) res = anchor.href; } return res; } EvoEditor.DialogUtilsGetImageWidth = function(natural) { var element = EvoEditor.getCurrentElement(), res = -1; if (element && element.tagName == "IMG") { if (natural) res = element.naturalWidth; else res = element.width; } return res; } EvoEditor.DialogUtilsGetImageHeight = function(natural) { var element = EvoEditor.getCurrentElement(), res = -1; if (element && element.tagName == "IMG") { if (natural) res = element.naturalHeight; else res = element.height; } return res; } EvoEditor.dialogUtilsForeachTableScope = function(scope, traversar, opType) { var cell = EvoEditor.getCurrentElement(); if (!cell) throw "EvoEditor.dialogUtilsForeachTableScope: Current cell not found"; traversar.selectionUpdater = EvoSelection.CreateUpdaterObject(); EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); try { var table = EvoEditor.getParentElement("TABLE", cell, true); var rowFunc = function(row, traversar) { var jj, length = row.cells.length; for (jj = 0; jj < length; jj++) { var cell = row.cells[length - jj - 1]; if (cell && !traversar.exec(cell)) return false; } return true; }; if (scope == EvoEditor.E_CONTENT_EDITOR_SCOPE_CELL) { traversar.exec(cell); } else if (scope == EvoEditor.E_CONTENT_EDITOR_SCOPE_COLUMN) { if (table) { var length = table.rows.length, ii, cellIndex = cell.cellIndex; for (ii = 0; ii < length; ii++) { var row = table.rows[length - ii - 1]; if (row && cellIndex < row.cells.length && !traversar.exec(row.cells[cellIndex])) break; } } } else if (scope == EvoEditor.E_CONTENT_EDITOR_SCOPE_ROW) { var row = EvoEditor.getParentElement("TR", cell, true); if (row) rowFunc(row, traversar); } else if (scope == EvoEditor.E_CONTENT_EDITOR_SCOPE_TABLE) { if (table) { var length = table.rows.length, ii; for (ii = 0; ii < length; ii++) { if (!rowFunc(table.rows[length - ii - 1], traversar)) break; } } } try { traversar.selectionUpdater.restore(); } catch (ex) { } EvoEditor.dialogUtilsTableEnsureCurrentElement(table); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); if (traversar.anyChanged) EvoEditor.EmitContentChanged(); } traversar.selectionUpdater = null; } EvoEditor.DialogUtilsTableSetAttribute = function(scope, attrName, attrValue) { var traversar = { attrName : attrName, attrValue : attrValue, anyChanged : false, exec : function(cell) { if (EvoEditor.setAttributeWithUndoRedo("", cell, this.attrName, this.attrValue)) this.anyChanged = true; return true; } }; EvoEditor.dialogUtilsForeachTableScope(scope, traversar, "TableSetAttribute::" + attrName); } EvoEditor.DialogUtilsTableGetCellIsHeader = function() { var element = EvoEditor.getCurrentElement(); return element && element.tagName == "TH"; } EvoEditor.DialogUtilsTableSetHeader = function(scope, isHeader) { var traversar = { isHeader : isHeader, selectionUpdater : null, anyChanged : false, exec : function(cell) { if ((!this.isHeader && cell.tagName == "TD") || (this.isHeader && cell.tagName == "TH")) return; this.anyChanged = true; var opType = this.isHeader ? "unsetheader" : "setheader"; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType, cell, cell, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var node = document.createElement(this.isHeader ? "TH" : "TD"); while(cell.firstChild) { node.append(cell.firstChild); } var ii; for (ii = 0; ii < cell.attributes.length; ii++) { node.setAttribute(cell.attributes[ii].name, cell.attributes[ii].value); } cell.parentElement.insertBefore(node, cell); if (this.selectionUpdater) this.selectionUpdater.beforeRemove(cell); cell.remove(); if (this.selectionUpdater) this.selectionUpdater.afterRemove(node); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType); } return true; } }; EvoEditor.dialogUtilsForeachTableScope(scope, traversar, "DialogUtilsTableSetHeader"); } EvoEditor.DialogUtilsTableGetRowCount = function() { var element = EvoEditor.getCurrentElement(); if (!element) return 0; element = EvoEditor.getParentElement("TABLE", element, true); if (!element) return 0; return element.rows.length; } EvoEditor.dialogUtilsTableEnsureCurrentElement = function(table) { if (table && !EvoEditor.getCurrentElement() && table.rows.length > 0) { EvoEditor.setCurrentElement(table.rows[0].cells.length > 0 ? table.rows[0].cells[0] : table); } } EvoEditor.DialogUtilsTableSetRowCount = function(rowCount) { var currentElem = EvoEditor.getCurrentElement(); if (!currentElem) return; var table = EvoEditor.getParentElement("TABLE", currentElem, true); if (!table || table.rows.length == rowCount) return; var selectionUpdater = EvoSelection.CreateUpdaterObject(); EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "DialogUtilsTableSetRowCount", table, table, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var ii; if (table.rows.length < rowCount) { var jj, nCells = table.rows.length ? table.rows[0].cells.length : 1; for (ii = table.rows.length; ii < rowCount; ii++) { var row; row = table.insertRow(-1); for (jj = 0; jj < nCells; jj++) { row.insertCell(-1); } } } else if (table.rows.length > rowCount) { for (ii = table.rows.length; ii > rowCount; ii--) { table.deleteRow(ii - 1); } } try { // it can fail, due to removed rows selectionUpdater.restore(); } catch (ex) { } EvoEditor.dialogUtilsTableEnsureCurrentElement(table); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "DialogUtilsTableSetRowCount"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.DialogUtilsTableGetColumnCount = function() { var element = EvoEditor.getCurrentElement(); if (!element) return 0; element = EvoEditor.getParentElement("TABLE", element, true); if (!element || !element.rows.length) return 0; return element.rows[0].cells.length; } EvoEditor.DialogUtilsTableSetColumnCount = function(columnCount) { var currentElem = EvoEditor.getCurrentElement(); if (!currentElem) return; var table = EvoEditor.getParentElement("TABLE", currentElem, true); if (!table || !table.rows.length || table.rows[0].cells.length == columnCount) return; var selectionUpdater = EvoSelection.CreateUpdaterObject(); EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "DialogUtilsTableSetColumnCount", table, table, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var ii, jj; for (jj = 0; jj < table.rows.length; jj++) { var row = table.rows[jj]; if (row.cells.length < columnCount) { for (ii = row.cells.length; ii < columnCount; ii++) { row.insertCell(-1); } } else if (row.cells.length > columnCount) { for (ii = row.cells.length; ii > columnCount; ii--) { row.deleteCell(ii - 1); } } } try { // it can fail, due to removed columns selectionUpdater.restore(); } catch (ex) { } EvoEditor.dialogUtilsTableEnsureCurrentElement(table); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "DialogUtilsTableSetColumnCount"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.DialogUtilsTableDeleteCellContent = function() { var traversar = { anyChanged : false, exec : function(cell) { if (cell.firstChild) { this.anyChanged = true; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeletecellcontent", cell, cell, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { while (cell.firstChild) { if (this.selectionUpdater) this.selectionUpdater.beforeRemove(cell.firstChild); cell.removeChild(cell.firstChild); if (this.selectionUpdater) this.selectionUpdater.afterRemove(cell); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeletecellcontent"); } } return true; } }; EvoEditor.dialogUtilsForeachTableScope(EvoEditor.E_CONTENT_EDITOR_SCOPE_CELL, traversar, "TableDeleteCellContent"); } EvoEditor.DialogUtilsTableDeleteColumn = function() { var traversar = { anyChanged : false, exec : function(cell) { this.anyChanged = true; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeletecolumn", cell, cell, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { if (this.selectionUpdater) { this.selectionUpdater.beforeRemove(cell); this.selectionUpdater.afterRemove(cell.nextElementSibling ? cell.nextElementSibling : cell.previousElementSibling); } cell.remove(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeletecolumn"); } return true; } }; EvoEditor.dialogUtilsForeachTableScope(EvoEditor.E_CONTENT_EDITOR_SCOPE_COLUMN, traversar, "TableDeleteColumn"); } EvoEditor.DialogUtilsTableDeleteRow = function() { var traversar = { anyChanged : false, exec : function(cell) { this.anyChanged = true; var row = cell.parentElement; if (!row) return false; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeleterow", row, row, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { row.parentElement.deleteRow(row.rowIndex); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "subdeleterow"); } return false; } }; EvoEditor.dialogUtilsForeachTableScope(EvoEditor.E_CONTENT_EDITOR_SCOPE_ROW, traversar, "TableDeleteColumn"); } EvoEditor.DialogUtilsTableDelete = function() { var element = EvoEditor.getCurrentElement(); if (!element) return; element = EvoEditor.getParentElement("TABLE", element, true); if (!element) return; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "TableDelete", element, element, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { element.remove(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "TableDelete"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } // 'what' can be "column" or "row", // 'where' can be lower than 0 for before/above, higher than 0 for after/below EvoEditor.DialogUtilsTableInsert = function(what, where) { if (what != "column" && what != "row") throw "EvoEditor.DialogUtilsTableInsert: 'what' (" + what + ") can be only 'column' or 'row'"; if (!where) throw "EvoEditor.DialogUtilsTableInsert: 'where' cannot be zero"; var cell, table; cell = EvoEditor.getCurrentElement(); if (!cell) return; table = EvoEditor.getParentElement("TABLE", cell, true); if (!table) return; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "TableInsert::" + what, table, table, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var index, ii; if (what == "column") { index = cell.cellIndex; if (where > 0) index++; for (ii = 0; ii < table.rows.length; ii++) { table.rows[ii].insertCell(index <= table.rows[ii].cells.length ? index : -1); } } else { // what == "row" var row = EvoEditor.getParentElement("TR", cell, true); if (row) { index = row.rowIndex; if (where > 0) index++; row = table.insertRow(index <= table.rows.length ? index : -1); for (ii = 0; ii < table.rows[0].cells.length; ii++) { row.insertCell(-1); } } } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "TableInsert::" + what); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.GetCaretWord = function() { if (document.getSelection().rangeCount < 1) return null; var range = document.getSelection().getRangeAt(0); if (!range) return null; range = range.cloneRange(); range.expand("word"); return range.toString(); } EvoEditor.replaceSelectionWord = function(opType, expandWord, replacement) { if (!expandWord && document.getSelection().isCollapsed) return; if (document.getSelection().rangeCount < 1) return; var range = document.getSelection().getRangeAt(0); if (!range) return; if (expandWord) range.expand("word"); EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_EVENT, opType, null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var fragment = range.extractContents(), node; /* Get the text node to replace and leave other formatting nodes * untouched (font color, boldness, ...). */ fragment.normalize(); for (node = fragment.firstChild; node && node.nodeType != node.TEXT_NODE; node = node.firstChild) { ; } if (node && node.nodeType == node.TEXT_NODE && replacement) { var text; /* Replace the word */ text = document.createTextNode(replacement); node.parentNode.replaceChild(text, node); /* Insert the word on current location. */ range.insertNode(fragment); document.getSelection().setPosition(text, replacement ? replacement.length : 0); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_EVENT, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.ReplaceCaretWord = function(replacement) { EvoEditor.replaceSelectionWord("ReplaceCaretWord", true, replacement); } EvoEditor.ReplaceSelection = function(replacement) { EvoEditor.replaceSelectionWord("ReplaceSelection", false, replacement); } EvoEditor.SpellCheckContinue = function(fromCaret, directionNext) { var selection, storedSelection = null; selection = document.getSelection(); if (fromCaret) { storedSelection = EvoSelection.Store(document); } else { if (directionNext) { selection.modify("move", "left", "documentboundary"); } else { selection.modify ("move", "right", "documentboundary"); selection.modify ("extend", "backward", "word"); } } var selectWord = function(selection, directionNext) { var anchorNode, anchorOffset; anchorNode = selection.anchorNode; anchorOffset = selection.anchorOffset; if (directionNext) { var focusNode, focusOffset; focusNode = selection.focusNode; focusOffset = selection.focusOffset; /* Jump _behind_ next word */ selection.modify("move", "forward", "word"); /* Jump before the word */ selection.modify("move", "backward", "word"); /* Select it */ selection.modify("extend", "forward", "word"); /* If the selection didn't change, then we have most probably reached the end of the document. */ return !((anchorNode === selection.anchorNode) && (anchorOffset == selection.anchorOffset) && (focusNode === selection.focusNode) && (focusOffset == selection.focusOffset)); } else { /* Jump on the beginning of current word */ selection.modify("move", "backward", "word"); /* Jump before previous word */ selection.modify("move", "backward", "word"); /* Select it */ selection.modify("extend", "forward", "word"); /* If the selection start didn't change, then we have most probably reached the beginning of the document. */ return (!(anchorNode === selection.anchorNode)) || (anchorOffset != selection.anchorOffset); } }; while (selectWord(selection, directionNext)) { if (selection.rangeCount < 1) break; var range = selection.getRangeAt(0); if (!range) break; var word = range.toString(); if (!EvoEditor.SpellCheckWord(word)) { /* Found misspelled word */ return word; } } /* Restore the selection to contain the last misspelled word. This is * reached only when we reach the beginning/end of the document */ if (storedSelection) EvoSelection.Restore(document, storedSelection); return null; } EvoEditor.MoveSelectionToPoint = function(xx, yy, cancel_if_not_collapsed) { if (!cancel_if_not_collapsed || document.getSelection().isCollapsed) { var range = document.caretRangeFromPoint(xx, yy); document.getSelection().removeAllRanges(); document.getSelection().addRange(range); } } EvoEditor.createEmoticonHTML = function(text, imageUri, width, height) { if (imageUri.toLowerCase().startsWith("file:")) imageUri = "evo-" + imageUri; if (imageUri && EvoEditor.mode == EvoEditor.MODE_HTML && !EvoEditor.UNICODE_SMILEYS) return "
"; return text; } EvoEditor.InsertEmoticon = function(text, imageUri, width, height) { EvoEditor.InsertHTML("InsertEmoticon", EvoEditor.createEmoticonHTML(text, imageUri, width, height)); } EvoEditor.InsertImage = function(imageUri, width, height) { if (imageUri.toLowerCase().startsWith("file:")) imageUri = "evo-" + imageUri; var html = "
0 && height > 0) { html += " width=\"" + width + "px\" height=\"" + height + "px\""; } html += ">"; EvoEditor.InsertHTML("InsertImage", html); } EvoEditor.GetCurrentSignatureUid = function() { var elem = document.querySelector(".-x-evo-signature[id]"); if (elem) return elem.id; return ""; } EvoEditor.insertEmptyParagraphBefore = function(beforeNode) { var node = document.createElement("DIV"); node.appendChild(document.createElement("BR")); document.body.insertBefore(node, beforeNode); EvoEditor.maybeUpdateParagraphWidth(node); return node; } EvoEditor.scrollIntoSelection = function() { var node = document.getSelection().focusNode; if (node) { if (node.nodeType != node.ELEMENT_NODE) node = node.parentElement; if (node && node.scrollIntoView != undefined) { node.scrollIntoView(); } } } EvoEditor.removeUnwantedTags = function(parent) { if (!parent) return; var child, next = null; for (child = parent.firstChild; child; child = next) { next = child.nextSibling; if (child.tagName == "STYLE" || child.tagName == "META") child.remove(); } } EvoEditor.InsertSignature = function(content, isHTML, canRepositionCaret, uid, fromMessage, checkChanged, ignoreNextChange, startBottom, topSignature, addDelimiter) { var sigSpan, node; sigSpan = document.createElement("SPAN"); sigSpan.className = "-x-evo-signature"; sigSpan.id = uid; if (content) { if (isHTML && EvoEditor.mode != EvoEditor.MODE_HTML) { node = document.createElement("SPAN"); node.innerHTML = content; EvoEditor.removeUnwantedTags(node); content = EvoConvert.ToPlainText(node, EvoEditor.NORMAL_PARAGRAPH_WIDTH); if (content != "") { content = "
" + content.replace(/\&/g, "&").replace(//g, ">") + ""; } isHTML = false; } /* The signature dash convention ("-- \n") is specified * in the "Son of RFC 1036", section 4.3.2. * http://www.chemie.fu-berlin.de/outerspace/netnews/son-of-1036.html */ if (addDelimiter) { var found; if (isHTML) { found = content.substr(0, 8).toUpperCase().startsWith("--
") || content.match(/\n--
/i) != null; } else { found = content.startsWith("-- \n") || content.match(/\n-- \n/i) != null; } /* Skip the delimiter if the signature already has one. */ if (!found) { /* Always use the HTML delimiter as we are never in anything * like a strict plain text mode. */ node = document.createElement("PRE"); node.innerHTML = "--
"; sigSpan.appendChild(node); } } sigSpan.insertAdjacentHTML("beforeend", content); node = sigSpan.querySelector("[data-evo-signature-plain-text-mode]"); if (node) node.removeAttribute("[data-evo-signature-plain-text-mode]"); node = sigSpan.querySelector("#-x-evo-selection-start-marker"); if (node) node.remove(); node = sigSpan.querySelector("#-x-evo-selection-end-marker"); if (node) node.remove(); EvoEditor.removeUnwantedTags(sigSpan); } EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "InsertSignature"); try { var signatures, ii, done = false, useWrapper = null; signatures = document.getElementsByClassName("-x-evo-signature-wrapper"); for (ii = signatures.length; ii-- && !done;) { var wrapper, signature; wrapper = signatures[ii]; signature = wrapper.firstElementChild; /* When we are editing a message with signature, we need to unset the * active signature id as if the signature in the message was edited * by the user we would discard these changes. */ if (fromMessage && content && signature) { if (checkChanged) { /* Normalize the signature that we want to insert as the one in the * message already is normalized. */ signature.normalize(); if (signature.firstElementChild && !signature.firstElementChild.isEqualNode(sigSpan)) { /* Signature in the body is different than the one with the * same id, so set the active signature to None and leave * the signature that is in the body. */ uid = "none"; ignoreNextChange = true; } checkChanged = false; fromMessage = false; } else { /* Old messages will have the signature id in the name attribute, correct it. */ if (signature.hasAttribute("name")) { id = signature.getAttribute("name"); signature.id = id; signature.removeAttribute("name"); } else { id = signature.id; } /* Keep the signature and check if is it the same * as the signature in body or the user previously * changed it. */ checkChanged = true; } done = true; } else { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::old-changes", wrapper, wrapper, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML | EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { /* If the top signature was set we have to remove the newline * that was inserted after it */ if (topSignature) { node = document.querySelector(".-x-evo-top-signature-spacer"); if (node && (!node.firstChild || !node.textContent || (node.childNodes.length == 1 && node.firstChild.tagName == "BR"))) { node.remove(); } } /* Leave just one signature wrapper there as it will be reused. */ if (ii) { wrapper.remove(); } else { wrapper.removeChild(signature); useWrapper = wrapper; } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::old-changes"); } } } if (!done) { if (useWrapper) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::new-changes", useWrapper, useWrapper, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { useWrapper.appendChild(sigSpan); /* Insert a spacer below the top signature */ if (topSignature && content) { node = document.createElement("DIV"); node.appendChild(document.createElement("BR")); node.className = "-x-evo-top-signature-spacer"; document.body.insertBefore(node, useWrapper.nextSibling); EvoEditor.maybeUpdateParagraphWidth(node); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::new-changes"); } } else { useWrapper = document.createElement("DIV"); useWrapper.className = "-x-evo-signature-wrapper"; useWrapper.appendChild(sigSpan); EvoEditor.maybeUpdateParagraphWidth(useWrapper); EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::new-changes", document.body, document.body, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var emptyDocument = !document.body.firstElementChild || !document.body.firstElementChild.nextElementSibling; if (topSignature && !emptyDocument) { document.body.insertBefore(useWrapper, document.body.firstChild); node = document.createElement("DIV"); node.appendChild(document.createElement("BR")); node.className = "-x-evo-top-signature-spacer"; document.body.insertBefore(node, useWrapper.nextSibling); // Insert empty paragraph before the signature EvoEditor.insertEmptyParagraphBefore(document.body.firstChild); } else { if (!startBottom && !emptyDocument) { // Insert empty paragraph before the signature EvoEditor.insertEmptyParagraphBefore(null); } document.body.appendChild(useWrapper); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertSignature::new-changes"); } } fromMessage = false; if (canRepositionCaret) { // Position the caret and scroll to it if (startBottom) { if (topSignature) { document.getSelection().setPosition(document.body.lastChild, 0); } else if (useWrapper.previousSibling) { document.getSelection().setPosition(useWrapper.previousSibling, 0); } else { document.getSelection().setPosition(useWrapper, 0); } } else { document.getSelection().setPosition(document.body.firstChild, 0); } EvoEditor.scrollIntoSelection(); } } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "InsertSignature"); } var res = []; res["fromMessage"] = fromMessage; res["checkChanged"] = checkChanged; res["ignoreNextChange"] = ignoreNextChange; res["newUid"] = uid; return res; } EvoEditor.isEmptyParagraph = function(node) { if (!node || !EvoEditor.IsBlockNode(node)) return false; return !node.children.length || (node.children.length == 1 && node.children[0].tagName == "BR"); } // replaces current selection with the plain text or HTML, quoted or normal DIV EvoEditor.InsertContent = function(text, isHTML, quote) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, "InsertContent"); try { if (!document.getSelection().isCollapsed) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::sel-remove", null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { document.getSelection().deleteFromDocument(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::sel-remove"); } } var wasPlain = !isHTML; var content = document.createElement(quote ? "BLOCKQUOTE" : "DIV"); if (quote) { content.setAttribute("type", "cite"); content.setAttribute("spellcheck", "false"); } if (isHTML) { content.innerHTML = text; // paste can contain elements, like the one with Content-Type, which can be removed while (content.firstElementChild && content.firstElementChild.tagName == "META") { content.removeChild(content.firstElementChild); } // remove comments at the beginning, like the Evolution's "" while (content.firstChild && content.firstChild.nodeType == content.firstChild.COMMENT_NODE) { content.removeChild(content.firstChild); } // convert P into DIV var node = content.firstChild, next; while (node) { var removeNode = false; if (node.nodeType == node.ELEMENT_NODE && node.tagName == "P") { removeNode = true; var div = document.createElement("DIV"); EvoEditor.moveNodeContent(node, div); node.parentElement.insertBefore(div, node.nextSibling); } next = EvoEditor.getNextNodeInHierarchy(node, content); if (removeNode) node.remove(); node = next; } if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { EvoEditor.convertParagraphs(content, quote ? 1 : 0, EvoEditor.NORMAL_PARAGRAPH_WIDTH, quote); content.innerText = EvoConvert.ToPlainText(content, EvoEditor.NORMAL_PARAGRAPH_WIDTH); } else { EvoEditor.convertParagraphs(content, quote ? 1 : 0, -1, quote); } } else { var lines = text.split("\n"); if (lines.length == 1 || (lines.length == 2 && !lines[1])) { content.innerText = lines[0]; } else { var ii, line, divNode; for (ii = 0; ii < lines.length; ii++) { line = lines[ii]; divNode = document.createElement("DIV"); content.appendChild(divNode); if (!line.length) { divNode.appendChild(document.createElement("BR")); } else { divNode.innerText = line; } } isHTML = true; } } if (EvoEditor.MAGIC_LINKS) { var node, next, covered = false; for (node = content.firstChild; node; node = next) { next = EvoEditor.getNextNodeInHierarchy(node, content); if (node.nodeType == node.TEXT_NODE) covered = EvoEditor.linkifyText(node, false) || covered; } if (covered && !isHTML) { EvoEditor.convertParagraphs(content, quote ? 1 : 0, EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT ? EvoEditor.NORMAL_PARAGRAPH_WIDTH : -1, quote); isHTML = true; } } if (quote) { if (!isHTML) EvoEditor.convertParagraphs(content, quote ? 1 : 0, EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT ? EvoEditor.NORMAL_PARAGRAPH_WIDTH : -1, quote); var anchorNode = document.getSelection().anchorNode, intoBody = false; if (!content.firstElementChild || (content.firstElementChild.tagName != "DIV" && content.firstElementChild.tagName != "P" && content.firstElementChild.tagName != "PRE")) { // enclose quoted text into DIV var node = document.createElement("DIV"); while (content.firstChild) { node.appendChild(content.firstChild); } content.appendChild(node); } if (anchorNode) { var node, parentBlock = null; if (anchorNode.nodeType == anchorNode.ELEMENT_NODE) { node = anchorNode; } else { node = anchorNode.parentElement; } while (node && node.tagName != "BODY" && !EvoEditor.IsBlockNode(node)) { parentBlock = node; node = node.parentElement; } if (node && node.tagName != "BLOCKQUOTE") parentBlock = node; else if (!parentBlock) parentBlock = node; if (!parentBlock) { intoBody = true; } else { var willSplit = parentBlock.tagName == "DIV" || parentBlock.tagName == "P" || parentBlock.tagName == "PRE"; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::text", parentBlock, parentBlock, (willSplit ? EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE : 0) | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { if (willSplit) { // need to split the content up to the parent block node if (anchorNode.nodeType == anchorNode.TEXT_NODE) { anchorNode.splitText(document.getSelection().anchorOffset); } var from = anchorNode.nextSibling, parent, nextFrom = null; parent = from ? from.parentElement : anchorNode.parentElement; if (!from && parent) { from = parent.nextElementSibling; nextFrom = from; parent = parent.parentElement; } while (parent && parent.tagName != "BODY") { nextFrom = null; if (from) { var clone; clone = from.parentElement.cloneNode(false); from.parentElement.parentElement.insertBefore(clone, from.parentElement.nextSibling); nextFrom = clone; while (from.nextSibling) { clone.appendChild(from.nextSibling); } clone.insertBefore(from, clone.firstChild); } if (parent === parentBlock.parentElement || (parent.parentElement && parent.parentElement.tagName == "BLOCKQUOTE")) { break; } from = nextFrom; parent = parent.parentElement; } } parentBlock.insertAdjacentElement("afterend", content); if (content.nextSibling) document.getSelection().setPosition(content.nextSibling, 0); else if (content.lastChild) { node = content.lastChild; while (node.lastChild) node = node.lastChild; document.getSelection().setPosition(node, node.nodeType == node.TEXT_NODE ? node.nodeValue.length : 0); } else document.getSelection().setPosition(content, 0); if (anchorNode.nodeType == anchorNode.ELEMENT_NODE && anchorNode.parentElement && EvoEditor.isEmptyParagraph(anchorNode)) { anchorNode.remove(); } else { anchorNode = parentBlock.nextSibling.nextSibling; if (anchorNode && anchorNode.nodeType == anchorNode.ELEMENT_NODE && anchorNode.parentElement && EvoEditor.isEmptyParagraph(anchorNode)) { anchorNode.remove(); } } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::text"); } } } else { intoBody = true; } if (intoBody) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::text", document.body, document.body, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { document.body.insertAdjacentElement("afterbegin", content); EvoEditor.maybeUpdateParagraphWidth(content); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::text"); } } if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { var ii; for (ii = 0; ii < content.children.length; ii++) { EvoEditor.requoteNodeParagraph(content.children[ii]); } } } else if (isHTML) { var list, ii; list = content.getElementsByTagName("BLOCKQUOTE"); for (ii = 0; ii < list.length; ii++) { var node = list[ii]; node.removeAttribute("class"); node.removeAttribute("style"); } var selection = document.getSelection(); var useOuterHTML = !list.length && !content.getElementsByTagName("DIV").length && !content.getElementsByTagName("PRE").length; if (!useOuterHTML && selection.isCollapsed && selection.focusNode && EvoEditor.isEmptyParagraph(selection.focusNode)) { var node = selection.focusNode, lastNode = null; EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::replaceEmptyBlock", node, node, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { if (useOuterHTML) { lastNode = content; node.parentElement.insertBefore(content, node); } else { while (content.firstChild) { lastNode = content.firstChild; node.parentElement.insertBefore(content.firstChild, node); } } node.remove(); if (lastNode) { while (lastNode.lastChild) { lastNode = lastNode.lastChild; } selection.setPosition(lastNode, lastNode.nodeType == lastNode.TEXT_NODE ? lastNode.nodeValue.length : 0); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "InsertContent::replaceEmptyBlock"); } EvoEditor.correctParagraphsAfterInsertContent("InsertContent::inEmptyBlock"); } else { useOuterHTML = useOuterHTML && !wasPlain; EvoEditor.InsertHTML("InsertContent::text", useOuterHTML ? content.outerHTML : content.innerHTML); } } else { EvoEditor.InsertText("InsertContent::text", content.innerText); } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, "InsertContent"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.splitPreTexts = function(node, isInPre, newNodes) { if (!node) return; isInPre = isInPre || node.tagName == "PRE"; var currPre = null, child, childIsPre, next; for (child = node.firstChild; child; child = next) { childIsPre = child.tagName == "PRE"; next = child.nextSibling; if (childIsPre || child.tagName == "BLOCKQUOTE") { currPre = null; var list = [], ii, clone = null; EvoEditor.splitPreTexts(child, isInPre, list); for (ii = 0; ii < list.length; ii++) { if (childIsPre) { newNodes[newNodes.length] = list[ii]; } else { if (!clone) { clone = child.cloneNode(false); newNodes[newNodes.length] = clone; } clone.appendChild(list[ii]); } } } else if (isInPre && child.nodeType == node.TEXT_NODE) { var text = child.nodeValue, pre, ii, lines; lines = text.split("\n"); for (ii = 0; ii < lines.length; ii++) { var line = lines[ii].replace(/\r/g, ""); //is shown as a block, thus adding a new line at the end behaves like two
-s if (!line && ii + 1 >= lines.length) { if (ii > 0) currPre = null; break; } if (ii == 0 && currPre) { if (line) currPre.appendChild(document.createTextNode(line)); if (lines.length > 1) currPre = null; continue; } pre = document.createElement("PRE"); if (line) { pre.innerText = line; } else { pre.appendChild(document.createElement("BR")); } currPre = pre; newNodes[newNodes.length] = pre; } } else if (currPre && child.tagName == "BR") { currPre = null; } else { child.remove(); if (currPre) { currPre.appendChild(child); } else if (isInPre) { currPre = document.createElement("PRE"); currPre.appendChild(child); newNodes[newNodes.length] = currPre; } else { newNodes[newNodes.length] = child; } } } } EvoEditor.traverseToRemoveInsignificantNewLines = function(parent) { if (!parent) return; var child; for (child = parent.firstChild; child; child = child.nextSibling) { if (child.nodeType == child.TEXT_NODE) { var str = EvoConvert.RemoveInsignificantNewLines(child); if (str != child.nodeValue) { child.nodeValue = str; } } else if (child.firstChild) { EvoEditor.traverseToRemoveInsignificantNewLines(child); } } } EvoEditor.processLoadedContent = function() { if (!document.body) return; var node, didCite, ii, list; if (!document.body.hasAttribute("data-evo-draft") && document.querySelector("PRE")) { var next, replacement; document.body.normalize(); for (node = document.body.firstChild; node; node = next) { next = node.nextSibling; if (node.tagName == "PRE" || node.tagName == "BLOCKQUOTE") { list = []; EvoEditor.splitPreTexts(node, false, list); for (ii = 0; ii < list.length; ii++) { node.parentElement.insertBefore(list[ii], node); } node.remove(); } } } // This is to have prepared the text nodes for plain text. The plain text mode // sets white-space for div-s to 'pre-wrap', which means the new lines are // significant, but before the conversion they are insignificant, because // the loaded content is regular HTML, not Plain Text-like HTML. EvoEditor.UpdateStyleSheet("processLoadedContent", "body div { white-space: normal; }"); try { EvoEditor.traverseToRemoveInsignificantNewLines(document.body); } finally { EvoEditor.UpdateStyleSheet("processLoadedContent", null); } node = document.querySelector("SPAN.-x-evo-cite-body"); didCite = node; if (node) node.remove(); if (didCite) { didCite = document.createElement("BLOCKQUOTE"); didCite.setAttribute("type", "cite"); didCite.setAttribute("spellcheck", "false"); while (document.body.firstChild) { didCite.appendChild(document.body.firstChild); } var next; // Evolution builds HTML with insignificant "\n", thus remove them first for (node = didCite.firstChild; node; node = next) { next = EvoEditor.getNextNodeInHierarchy(node, didCite); if (node.nodeType == node.TEXT_NODE && node.nodeValue && node.nodeValue.charAt(0) == '\n' && ( (node.previousSibling && EvoEditor.IsBlockNode(node.previousSibling)) || (!node.previousSibling && node.parentElement.tagName == "BLOCKQUOTE" && !(node.parentElement === didCite)))) { node.nodeValue = node.nodeValue.substr(1); } } document.body.appendChild(didCite); } list = document.querySelectorAll("STYLE[id]"); for (ii = list.length - 1; ii >= 0; ii--) { node = list[ii]; if (node.id && node.id.startsWith("-x-evo-")) node.remove(); } list = document.querySelectorAll("DIV[data-headers]"); for (ii = list.length - 1; ii >= 0; ii--) { node = list[ii]; node.removeAttribute("data-headers"); document.body.insertAdjacentElement("afterbegin", node); } list = document.querySelectorAll("SPAN.-x-evo-to-body[data-credits]"); for (ii = list.length - 1; ii >= 0; ii--) { node = list[ii]; var credits = node.getAttribute("data-credits"); if (credits) { var elem; elem = document.createElement("DIV"); elem.innerText = credits; document.body.insertAdjacentElement("afterbegin", elem); EvoEditor.maybeUpdateParagraphWidth(elem); } node.remove(); } list = document.querySelectorAll(".-x-evo-paragraph"); for (ii = list.length - 1; ii >= 0; ii--) { node = list[ii]; node.removeAttribute("class"); } list = document.querySelectorAll("[data-evo-paragraph]"); for (ii = list.length - 1; ii >= 0; ii--) { list[ii].removeAttribute("data-evo-paragraph"); } // require blocks under BLOCKQUOTE and style them properly list = document.getElementsByTagName("BLOCKQUOTE"); for (ii = list.length - 1; ii >= 0; ii--) { var blockquoteNode = list[ii], addingTo = null, next; for (node = blockquoteNode.firstChild; node; node = next) { next = node.nextSibling; if (!EvoEditor.IsBlockNode(node) && (node.nodeType == node.ELEMENT_NODE || (node.nodeValue && node.nodeValue != "\n" && node.nodeValue != "\r\n"))) { if (!addingTo) { addingTo = document.createElement(EvoEditor.hasElementWithTagNameAsParent(node, "PRE") ? "PRE" : "DIV"); blockquoteNode.insertBefore(addingTo, node); EvoEditor.maybeUpdateParagraphWidth(addingTo); } addingTo.appendChild(node); } else { addingTo = null; } } if (blockquoteNode.className == "gmail_quote") { if (blockquoteNode.lastChild && blockquoteNode.lastChild.tagName != "BR" && blockquoteNode.nextSibling) { blockquoteNode.appendChild(document.createElement("BR")); } } if (blockquoteNode.previousSibling && blockquoteNode.previousSibling.nodeType == blockquoteNode.TEXT_NODE && blockquoteNode.previousSibling.nodeValue) { blockquoteNode.parentElement.insertBefore(document.createElement("BR"), blockquoteNode); } blockquoteNode.removeAttribute("class"); blockquoteNode.removeAttribute("style"); blockquoteNode.setAttribute("type", "cite"); blockquoteNode.setAttribute("spellcheck", "false"); } if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { EvoEditor.convertParagraphs(document.body, 0, EvoEditor.NORMAL_PARAGRAPH_WIDTH, didCite); if (EvoEditor.MAGIC_LINKS) { var next; for (node = document.body.firstChild; node; node = next) { next = EvoEditor.getNextNodeInHierarchy(node, null); if (node.nodeType == node.TEXT_NODE) EvoEditor.linkifyText(node, false); } } EvoEditor.cleanupForPlainText(); } else { // drop margin/padding-related attributes and styles var unsetMarginPadding = function(elem, style) { if (elem) { var ii; for (ii = elem.attributes.length - 1; ii >= 0; ii--) { var name = elem.attributes[ii].nodeName; if (!name) continue; name = name.toLowerCase(); if (name.indexOf("margin") >= 0 || name.indexOf("padding") >= 0) elem.removeAttribute(name); } if (!style) style = elem.style; } if (!style) return false; var changed = false; if (style.margin) { style.margin = null; changed = true; } if (style.marginLeft) { style.marginLeft = null; changed = true; } if (style.marginTop) { style.marginTop = null; changed = true; } if (style.marginRight) { style.marginRight = null; changed = true; } if (style.marginBottom) { style.marginBottom = null; changed = true; } if (style.padding) { style.padding = null; changed = true; } if (style.paddingLeft) { style.paddingLeft = null; changed = true; } if (style.paddingTop) { style.paddingTop = null; changed = true; } if (style.paddingRight) { style.paddingRight = null; changed = true; } if (style.paddingBottom) { style.paddingBottom = null; changed = true; } return changed; }; unsetMarginPadding(document.documentElement); unsetMarginPadding(document.body); var ii; for (ii = document.styleSheets.length - 1; ii >= 0; ii--) { var sheet = document.styleSheets[ii]; if (!sheet.ownerNode) continue; var rules = sheet.cssRules; if (rules) { var jj, newCss = null; for (jj = 0; jj < rules.length; jj++) { if (rules[jj].selectorText && rules[jj].selectorText.toLowerCase().indexOf("body") >= 0) { if (unsetMarginPadding(null, rules[jj].style)) { if (newCss === null) { var kk; newCss = ""; for (kk = 0; kk < jj; kk++) { if (newCss) newCss += "\n"; newCss += rules[kk].cssText; } } if (rules[jj].style.cssText) { if (newCss) newCss += "\n"; newCss += rules[jj].cssText; } } } else if (newCss !== null) { if (newCss) newCss += "\n"; newCss += rules[jj].cssText; } } if (newCss !== null) { if (newCss) sheet.ownerNode.innerHTML = newCss; else sheet.ownerNode.remove(); } } } } // remove comments at the beginning, like the Evolution's "" while (document.documentElement.firstChild && document.documentElement.firstChild.nodeType == document.documentElement.firstChild.COMMENT_NODE) { document.documentElement.removeChild(document.documentElement.firstChild); } // remove unneeded data from the as well if (document.head) { list = document.head.childNodes; for (ii = list.length; ii >= 0; ii--) { node = list[ii]; if (node && (node.nodeType == node.COMMENT_NODE || node.tagName == "META")) document.head.removeChild(node); } } document.body.removeAttribute("data-evo-draft"); document.body.removeAttribute("data-evo-plain-text"); document.body.removeAttribute("spellcheck"); list = document.querySelectorAll("[id=-x-evo-input-start]"); for (ii = list.length - 1; ii >= 0; ii--) { node = list[ii]; node.removeAttribute("id"); document.getSelection().setPosition(node, 0); node.scrollIntoView(); } if (EvoEditor.START_BOTTOM && document.body.firstElementChild && document.body.firstElementChild.nextElementSibling) { node = EvoEditor.insertEmptyParagraphBefore(null); document.getSelection().setPosition(node, 0); node.scrollIntoView(); } else { EvoEditor.scrollIntoSelection(); } } EvoEditor.LoadHTML = function(html) { EvoUndoRedo.Disable(); try { var themeCss = EvoEditor.UpdateThemeStyleSheet(null); document.documentElement.innerHTML = html; EvoEditor.processLoadedContent(); EvoEditor.initializeContent(); if (themeCss) EvoEditor.UpdateThemeStyleSheet(themeCss); } finally { EvoUndoRedo.Enable(); EvoUndoRedo.Clear(); } } EvoEditor.wrapParagraph = function(paragraphNode, maxLetters, currentPar, usedLetters, wasNestedElem) { var child = paragraphNode.firstChild, nextChild, appendBR; while (child) { appendBR = false; if (child.nodeType == child.TEXT_NODE) { var text = child.nodeValue; // merge consecutive text nodes into one (similar to paragraphNode.normalize()) while (child.nextSibling && child.nextSibling.nodeType == child.TEXT_NODE) { nextChild = child.nextSibling; text += nextChild.nodeValue; child.remove(); child = nextChild; } while (text.length + usedLetters > maxLetters) { var spacePos = text.lastIndexOf(" ", maxLetters - usedLetters); if (spacePos < 0) spacePos = text.indexOf(" "); if (spacePos > 0 && (!usedLetters || usedLetters + spacePos <= maxLetters)) { var textNode = document.createTextNode(((usedLetters > 0 && !wasNestedElem) ? " " : "") + text.substr(0, spacePos)); if (currentPar) currentPar.appendChild(textNode); else child.parentElement.insertBefore(textNode, child); text = text.substr(spacePos + 1); } if (currentPar) currentPar.appendChild(document.createElement("BR")); else child.parentElement.insertBefore(document.createElement("BR"), child); usedLetters = 0; if (spacePos == 0) text = text.substr(1); else if (spacePos < 0) break; } child.nodeValue = ((usedLetters > 0 && !wasNestedElem) ? " " : "") + text; usedLetters += ((usedLetters > 0 && !wasNestedElem) ? 1 : 0) + text.length; if (usedLetters > maxLetters) appendBR = true; wasNestedElem = false; } else if (child.tagName == "BR") { wasNestedElem = false; if (!child.nextSibling) { return -1; } if (child.nextSibling.tagName == "BR") { usedLetters = 0; if (currentPar) { var nextSibling = child.nextSibling; nextChild = child.nextSibling.nextSibling; currentPar.appendChild(child); if (usedLetters) { currentPar.appendChild(nextSibling); } else { nextSibling.remove(); } child = nextChild; continue; } } else { nextChild = child.nextSibling; child.remove(); child = nextChild; continue; } } else if (child.tagName == "IMG") { // just skip it, do not count it into the line length wasNestedElem = false; } else if (child.tagName == "B" || child.tagName == "I" || child.tagName == "U" || child.tagName == "S" || child.tagName == "SUB" || child.tagName == "SUP" || child.tagName == "FONT" || child.tagName == "SPAN" || child.tagName == "A") { usedLetters = EvoEditor.wrapParagraph(child, maxLetters, null, usedLetters, true); if (usedLetters == -1) usedLetters = 0; wasNestedElem = true; } else if (child.nodeType == child.ELEMENT_NODE) { // everything else works like a line stopper, with a new line added after it appendBR = true; wasNestedElem = false; } nextChild = child.nextSibling; if (currentPar) currentPar.appendChild(child); if (appendBR) { usedLetters = 0; if (nextChild) { if (currentPar) currentPar.appendChild(document.createElement("BR")); else nextChild.parentElement.insertBefore(document.createElement("BR"), nextChild); } } child = nextChild; } return usedLetters; } EvoEditor.WrapSelection = function() { var nodeFrom, nodeTo; nodeFrom = EvoEditor.GetParentBlockNode(document.getSelection().anchorNode); nodeTo = EvoEditor.GetParentBlockNode(document.getSelection().focusNode); if (!nodeFrom || !nodeTo) { return; } if (nodeFrom != nodeTo) { // selection can go from top to bottom, but also from bottom to top; normalize the path order var commonParent = EvoEditor.GetCommonParent(nodeFrom, nodeTo, true), childFrom, childTo, ii, sz; childFrom = nodeFrom; while (childFrom && childFrom != commonParent && childFrom.parentElement != commonParent) { childFrom = childFrom.parentElement; } childTo = nodeTo; while (childTo && childTo != commonParent && childTo.parentElement != commonParent) { childTo = childTo.parentElement; } if (!childFrom || !childTo) { throw "EvoEditor.WrapSelection: Should not be reached (childFrom and childTo cannot be null)"; } sz = commonParent.children.length; for (ii = 0; ii < sz; ii++) { if (commonParent.children[ii] === childFrom) { nodeFrom = childFrom; nodeTo = childTo; break; } else if (commonParent.children[ii] === childTo) { nodeFrom = childTo; nodeTo = childFrom; break; } } } EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "WrapSelection", nodeFrom, nodeTo, EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { var maxLetters, usedLetters, currentPar, lastParTagName = nodeFrom.tagName; maxLetters = EvoEditor.NORMAL_PARAGRAPH_WIDTH; usedLetters = 0; currentPar = null; while (nodeFrom) { EvoEditor.removeQuoteMarks(nodeFrom); if (lastParTagName != nodeFrom.tagName) { lastParTagName = nodeFrom.tagName; currentPar = null; usedLetters = 0; } if (nodeFrom.tagName == "DIV" || nodeFrom.tagName == "P" || nodeFrom.tagName == "PRE") { if (nodeFrom.childNodes.length == 1 && nodeFrom.childNodes[0].tagName == "BR") { currentPar = null; usedLetters = 0; } else { var blockquoteLevel = 0; if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) blockquoteLevel = EvoEditor.getBlockquoteLevel(nodeFrom); usedLetters = EvoEditor.wrapParagraph(nodeFrom, maxLetters - (2 * blockquoteLevel), currentPar, usedLetters, false); if (blockquoteLevel) EvoEditor.requoteNodeParagraph(nodeFrom); if (usedLetters == -1) { currentPar = null; usedLetters = 0; } else if (!currentPar) { currentPar = nodeFrom; } } } // cannot break the cycle now, because want to delete the last empty paragraph var done = nodeFrom === nodeTo; if (!nodeFrom.childNodes.length) { var node = nodeFrom; nodeFrom = nodeFrom.nextSibling; if (node.parentElement) node.remove(); } else { nodeFrom = nodeFrom.nextSibling; } if (done) break; } // Place the cursor at the end of the wrapped paragraph(s) if (currentPar) nodeTo = currentPar; while (nodeTo.lastChild) { nodeTo = nodeTo.lastChild; } document.getSelection().setPosition(nodeTo, nodeTo.nodeType == nodeTo.TEXT_NODE ? nodeTo.nodeValue.length : 0); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "WrapSelection"); } } EvoEditor.onContextMenu = function(event) { var node = event.target; if (!node) node = document.getSelection().focusNode; if (!node) node = document.getSelection().anchorNode; EvoEditor.contextMenuNode = node; var nodeFlags = EvoEditor.E_CONTENT_EDITOR_NODE_UNKNOWN, res; while (node && node.tagName != "BODY") { if (node.tagName == "A") nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_ANCHOR; else if (node.tagName == "HR") nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_H_RULE; else if (node.tagName == "IMG") nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_IMAGE; else if (node.tagName == "TABLE") nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_TABLE; else if (node.tagName == "TD" || node.tagName == "TH") nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_TABLE_CELL; node = node.parentElement; } if (!nodeFlags && EvoEditor.contextMenuNode) nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_TEXT; if (document.getSelection().isCollapsed) nodeFlags |= EvoEditor.E_CONTENT_EDITOR_NODE_IS_TEXT_COLLAPSED; res = []; res["nodeFlags"] = nodeFlags; res["caretWord"] = EvoEditor.GetCaretWord(); window.webkit.messageHandlers.contextMenuRequested.postMessage(res); } document.oncontextmenu = EvoEditor.onContextMenu; document.onload = EvoEditor.initializeContent; document.onselectionchange = function() { if (EvoEditor.checkInheritFontsOnChange) { EvoEditor.checkInheritFontsOnChange = false; EvoEditor.maybeReplaceInheritFonts(); } EvoEditor.maybeUpdateFormattingState(EvoEditor.forceFormatStateUpdate ? EvoEditor.FORCE_YES : EvoEditor.FORCE_MAYBE); EvoEditor.forceFormatStateUpdate = false; window.webkit.messageHandlers.selectionChanged.postMessage(document.getSelection().isCollapsed); }; EvoEditor.initializeContent();