/* -*- 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 . */ 'use strict'; /* semi-convention: private functions start with lower-case letter, public functions start with upper-case letter. */ var EvoEditor = { CURRENT_ELEMENT_ATTR : "x-evo-dialog-current-element", BLOCKQUOTE_STYLE : "margin:0 0 0 .8ex; border-left:2px #729fcf solid;padding-left:1ex", E_CONTENT_EDITOR_ALIGNMENT_NONE : -1, E_CONTENT_EDITOR_ALIGNMENT_LEFT : 0, E_CONTENT_EDITOR_ALIGNMENT_CENTER : 1, E_CONTENT_EDITOR_ALIGNMENT_RIGHT : 2, E_CONTENT_EDITOR_ALIGNMENT_JUSTIFY : 3, E_CONTENT_EDITOR_BLOCK_FORMAT_NONE : 0, E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH : 1, E_CONTENT_EDITOR_BLOCK_FORMAT_PRE : 2, E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS : 3, E_CONTENT_EDITOR_BLOCK_FORMAT_H1 : 4, E_CONTENT_EDITOR_BLOCK_FORMAT_H2 : 5, E_CONTENT_EDITOR_BLOCK_FORMAT_H3 : 6, E_CONTENT_EDITOR_BLOCK_FORMAT_H4 : 7, E_CONTENT_EDITOR_BLOCK_FORMAT_H5 : 8, E_CONTENT_EDITOR_BLOCK_FORMAT_H6 : 9, E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST : 10, E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST : 11, E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN : 12, E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA : 13, E_CONTENT_EDITOR_GET_INLINE_IMAGES : 1 << 0, E_CONTENT_EDITOR_GET_RAW_BODY_HTML : 1 << 1, E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN : 1 << 2, E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED : 1 << 3, E_CONTENT_EDITOR_GET_RAW_DRAFT : 1 << 4, E_CONTENT_EDITOR_GET_TO_SEND_HTML : 1 << 5, E_CONTENT_EDITOR_GET_TO_SEND_PLAIN : 1 << 6, E_CONTENT_EDITOR_NODE_UNKNOWN : 0, E_CONTENT_EDITOR_NODE_IS_ANCHOR : 1 << 0, E_CONTENT_EDITOR_NODE_IS_H_RULE : 1 << 1, E_CONTENT_EDITOR_NODE_IS_IMAGE : 1 << 2, E_CONTENT_EDITOR_NODE_IS_TABLE : 1 << 3, E_CONTENT_EDITOR_NODE_IS_TABLE_CELL : 1 << 4, E_CONTENT_EDITOR_NODE_IS_TEXT : 1 << 5, E_CONTENT_EDITOR_NODE_IS_TEXT_COLLAPSED : 1 << 6, E_CONTENT_EDITOR_SCOPE_CELL : 0, E_CONTENT_EDITOR_SCOPE_ROW : 1, E_CONTENT_EDITOR_SCOPE_COLUMN : 2, E_CONTENT_EDITOR_SCOPE_TABLE : 3, /* Flags for ClaimAffectedContent() */ CLAIM_CONTENT_FLAG_NONE : 0, CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE : 1 << 0, CLAIM_CONTENT_FLAG_SAVE_HTML : 1 << 1, TEXT_INDENT_SIZE : 3, // in characters NORMAL_PARAGRAPH_WIDTH : 71, MAGIC_LINKS : true, MAGIC_SMILEYS : false, UNICODE_SMILEYS : false, WRAP_QUOTED_TEXT_IN_REPLIES : true, START_BOTTOM : false, FORCE_NO : 0, FORCE_YES : 1, FORCE_MAYBE : 2, MODE_PLAIN_TEXT : 0, MODE_HTML : 1, mode : 1, // one of the MODE constants storedSelection : null, propertiesSelection : null, // dedicated to Properties dialogs contextMenuNode : null, // the last target node for context menu inheritThemeColors : false, checkInheritFontsOnChange : false, forceFormatStateUpdate : false, formattingState : { mode : -1, anchorElement : null, // to avoid often notifications when just moving within the same node bold : false, italic : false, underline : false, strikethrough : false, script : 0, // -1..subscript, 0..normal, +1..superscript blockFormat : -1, alignment : -1, fgColor : null, bgColor : null, fontSize : null, fontFamily : null, indentLevel : 0, bodyFgColor : null, bodyBgColor : null, bodyLinkColor : null, bodyVlinkColor : null, bodyFontFamily : null } }; EvoEditor.maybeUpdateFormattingState = function(force) { var anchorElem = null; if (!document.getSelection().isCollapsed) { var commonParent; commonParent = EvoEditor.GetCommonParent(document.getSelection().anchorNode, document.getSelection().focusNode, true); if (commonParent) { var child1, child2; child1 = EvoEditor.GetDirectChild(commonParent, document.getSelection().anchorNode); child2 = EvoEditor.GetDirectChild(commonParent, document.getSelection().focusNode); if (child1 && (!child2 || (child2 && EvoEditor.GetChildIndex(commonParent, child1) <= EvoEditor.GetChildIndex(commonParent, child2)))) { anchorElem = document.getSelection().focusNode; } } } if (!anchorElem) anchorElem = document.getSelection().anchorNode; if (!anchorElem) anchorElem = document.body ? document.body.firstElementChild : null; if (anchorElem && anchorElem.nodeType == anchorElem.TEXT_NODE) anchorElem = anchorElem.parentElement; if (force == EvoEditor.FORCE_NO && EvoEditor.formattingState.anchorElement === anchorElem && EvoEditor.mode == EvoEditor.formattingState.mode) { return; } force = force == EvoEditor.FORCE_YES; EvoEditor.formattingState.anchorElement = anchorElem; var changes = {}, nchanges = 0, value, tmp; value = EvoEditor.mode; if (value != EvoEditor.formattingState.mode) { EvoEditor.formattingState.mode = value; changes["mode"] = value; nchanges++; } value = document.body ? document.body.style.fontFamily : ""; if (force || value != EvoEditor.formattingState.bodyFontFamily) { EvoEditor.formattingState.bodyFontFamily = value; changes["bodyFontFamily"] = value; nchanges++; } value = document.body ? document.body.text : ""; if (force || value != EvoEditor.formattingState.bodyFgColor) { EvoEditor.formattingState.bodyFgColor = value; changes["bodyFgColor"] = value; nchanges++; } value = document.body.bgColor; if (force || value != EvoEditor.formattingState.bodyBgColor) { EvoEditor.formattingState.bodyBgColor = value; changes["bodyBgColor"] = value; nchanges++; } value = document.body.link; if (force || value != EvoEditor.formattingState.bodyLinkColor) { EvoEditor.formattingState.bodyLinkColor = value; changes["bodyLinkColor"] = value; nchanges++; } value = document.body.vLink; if (force || value != EvoEditor.formattingState.bodyVlinkColor) { EvoEditor.formattingState.bodyVlinkColor = value; changes["bodyVlinkColor"] = value; nchanges++; } var parent, obj = { script : 0, blockFormat : null, indentLevel : 0, fontFamily : null, fontSize : null, fgColor : null, bgColor : null, bold : null, italic : null, underline : null, strikethrough : null, textAlign : null }; for (parent = anchorElem; parent && !(parent === document.body); parent = parent.parentElement) { if (obj.script == 0) { if (parent.tagName == "SUB") obj.script = -1; else if (parent.tagName == "SUP") obj.script = +1; } if (obj.blockFormat === null) { if (parent.tagName == "DIV") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH; else if (parent.tagName == "PRE") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_PRE; else if (parent.tagName == "ADDRESS") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS; else if (parent.tagName == "H1") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H1; else if (parent.tagName == "H2") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H2; else if (parent.tagName == "H3") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H3; else if (parent.tagName == "H4") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H4; else if (parent.tagName == "H5") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H5; else if (parent.tagName == "H6") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H6; else if (parent.tagName == "UL") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST; else if (parent.tagName == "OL") { obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST; var typeAttr = parent.getAttribute("type"); if (typeAttr && typeAttr.toUpperCase() == "I") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN; else if (typeAttr && typeAttr.toUpperCase() == "A") obj.blockFormat = EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA; } } if ((obj.fontSize === null || obj.fontFamily === null || obj.fgColor === null) && parent.tagName == "FONT") { if (obj.fontSize === null && parent.hasAttribute("size")) { value = parent.getAttribute("size"); value = value ? parseInt(value, 10) : 0; if (Number.isInteger(value) && value >= 1 && value <= 7) { obj.fontSize = value; } } if (obj.fontFamily === null && parent.hasAttribute("face")) obj.fontFamily = parent.getAttribute("face"); if (obj.fgColor === null && parent.hasAttribute("color")) obj.fgColor = parent.getAttribute("color"); } var dir = window.getComputedStyle(parent).direction; if (dir == "rtl") { tmp = parent.style.marginRight; if (tmp && tmp.endsWith("ch")) { tmp = parseInt(tmp.slice(0, -2)); } else { tmp = ""; } } else { // "ltr" or other tmp = parent.style.marginLeft; if (tmp && tmp.endsWith("ch")) { tmp = parseInt(tmp.slice(0, -2)); } else { tmp = ""; } } if (Number.isInteger(tmp) && tmp > 0) { obj.indentLevel += tmp / EvoEditor.TEXT_INDENT_SIZE; } if (parent.tagName == "UL" || parent.tagName == "OL") obj.indentLevel++; if (obj.bgColor === null && parent.style.backgroundColor) obj.bgColor = parent.style.backgroundColor; if (obj.bold === null && parent.tagName == "B") obj.bold = true; if (obj.italic === null && parent.tagName == "I") obj.italic = true; if (obj.underline === null && parent.tagName == "U") obj.underline = true; if (obj.strikethrough === null && (parent.tagName == "S" || parent.tagName == "STRIKE")) obj.strikethrough = true; if (obj.textAlign === null && parent.style.textAlign) obj.textAlign = parent.style.textAlign; } value = obj.script; if (force || value != EvoEditor.formattingState.script) { EvoEditor.formattingState.script = value; changes["script"] = value; nchanges++; } value = obj.blockFormat === null ? EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH : obj.blockFormat; if (force || value != EvoEditor.formattingState.blockFormat) { EvoEditor.formattingState.blockFormat = value; changes["blockFormat"] = value; nchanges++; } value = obj.fontSize === null ? 3 /* E_CONTENT_EDITOR_FONT_SIZE_NORMAL */ : obj.fontSize; if (force || value != EvoEditor.formattingState.fontSize) { EvoEditor.formattingState.fontSize = value; changes["fontSize"] = value; nchanges++; } value = obj.indentLevel; if (force || value != EvoEditor.formattingState.indentLevel) { EvoEditor.formattingState.indentLevel = value; changes["indentLevel"] = value; nchanges++; } value = obj.fgColor ? obj.fgColor : ""; if (force || value != EvoEditor.formattingState.fgColor) { EvoEditor.formattingState.fgColor = value; changes["fgColor"] = value; nchanges++; } value = obj.bgColor ? obj.bgColor : ""; if (force || value != EvoEditor.formattingState.bgColor) { EvoEditor.formattingState.bgColor = value; changes["bgColor"] = value; nchanges++; } value = obj.bold ? true : false; if (value != EvoEditor.formattingState.bold) { EvoEditor.formattingState.bold = value; changes["bold"] = value; nchanges++; } value = obj.italic ? true : false; if (force || value != EvoEditor.formattingState.italic) { EvoEditor.formattingState.italic = value; changes["italic"] = value; nchanges++; } value = obj.underline ? true : false; if (force || value != EvoEditor.formattingState.underline) { EvoEditor.formattingState.underline = value; changes["underline"] = value; nchanges++; } value = obj.strikethrough ? true : false; if (force || value != EvoEditor.formattingState.strikethrough) { EvoEditor.formattingState.strikethrough = value; changes["strikethrough"] = value; nchanges++; } value = obj.fontFamily ? obj.fontFamily : ""; // dequote the font name, if needed if (value.length > 1 && value.charAt(0) == '\"' && value.charAt(value.length - 1) == '\"') value = value.substr(1, value.length - 2); if (force || value != EvoEditor.formattingState.fontFamily) { EvoEditor.formattingState.fontFamily = value; changes["fontFamily"] = (!document.body || window.getComputedStyle(document.body).fontFamily == value) ? "" : value; nchanges++; } tmp = (obj.textAlign ? obj.textAlign : "").toLowerCase(); if (tmp == "left" || tmp == "start") value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_LEFT; else if (tmp == "right") value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_RIGHT; else if (tmp == "center") value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_CENTER; else if (tmp == "justify") value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_JUSTIFY; else if ((anchorElem ? window.getComputedStyle(anchorElem).direction : "").toLowerCase() == "rtl") value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_RIGHT; else value = EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_LEFT; if (force || value != EvoEditor.formattingState.alignment) { EvoEditor.formattingState.alignment = value; changes["alignment"] = value; nchanges++; } if (force) { changes["forced"] = true; nchanges++; } if (nchanges > 0) window.webkit.messageHandlers.formattingChanged.postMessage(changes); } EvoEditor.IsBlockNode = function(node) { if (!node || !node.tagName) { return false; } return node.tagName == "BLOCKQUOTE" || node.tagName == "DIV" || node.tagName == "P" || node.tagName == "PRE" || node.tagName == "ADDRESS" || node.tagName == "H1" || node.tagName == "H2" || node.tagName == "H3" || node.tagName == "H4" || node.tagName == "H5" || node.tagName == "H6" || node.tagName == "TD" || node.tagName == "TH" || node.tagName == "UL" || node.tagName == "OL"; } EvoEditor.foreachChildRecur = function(topParent, parent, firstChildIndex, lastChildIndex, traversar) { if (!parent) { return false; } if (firstChildIndex >= parent.children.length) { return true; } var ii, child, next; ii = lastChildIndex - firstChildIndex; child = parent.children.item(firstChildIndex); while (child && ii >= 0) { next = child.nextElementSibling; if (child.children.length > 0 && !traversar.flat && !EvoEditor.foreachChildRecur(topParent, child, 0, child.children.length - 1, traversar)) { return false; } if (!traversar.onlyBlockElements || EvoEditor.IsBlockNode(child)) { if (!traversar.exec(topParent, child)) { return false; } } child = next; ii--; } return true; } /* Traverses children of the 'parent', between the 'firstChildIndex' and the 'lastChildIndex', where both indexes are meant inclusive. The 'traversar' is an object, which should contain at least function: bool exec(parent, element); which does its work in the 'element' and returns true, when the traversar should continue. The 'parent' is the one with which the funcion had been called with. The 'traversar' can also contain properties: bool flat; bool onlyBlockElements; the 'flat', if set to true, traverses only direct children of the parent, otherwise it dives into the hierarchy; the 'onlyBlockElements', if set to true, calls exec() only on elements, which are block elements (as of EvoEditor.IsBlockNode()), otherwise it is called for each element on the way. */ EvoEditor.ForeachChild = function(parent, firstChildIndex, lastChildIndex, traversar) { return EvoEditor.foreachChildRecur(parent, parent, firstChildIndex, lastChildIndex, traversar); } EvoEditor.GetParentBlockNode = function(node) { while (node && !EvoEditor.IsBlockNode(node) && node.tagName != "BODY") { node = node.parentElement; } return node; } EvoEditor.GetCommonParent = function(firstNode, secondNode, longPath) { if (!firstNode || !secondNode) { return null; } if (firstNode.nodeType == firstNode.TEXT_NODE) { firstNode = firstNode.parentElement; } if (secondNode.nodeType == secondNode.TEXT_NODE) { secondNode = secondNode.parentElement; } if (!firstNode || !secondNode) { return null; } if (firstNode === document.body || secondNode === document.body) { return document.body; } var commonParent, secondParent; for (commonParent = (longPath ? firstNode : firstNode.parentElement); commonParent; commonParent = commonParent.parentElement) { if (commonParent === document.body) { break; } for (secondParent = (longPath ? secondNode : secondNode.parentElement); secondParent; secondParent = secondParent.parentElement) { if (secondParent === document.body) { break; } if (secondParent === commonParent) { return commonParent; } } } return document.body; } EvoEditor.GetDirectChild = function(parent, child) { if (!parent || !child || parent === child) { return null; } while (child && !(child.parentElement === parent)) { child = child.parentElement; } return child; } EvoEditor.GetChildIndex = function(parent, child) { if (!parent || !child) return -1; var ii; for (ii = 0; ii < parent.children.length; ii++) { if (child === parent.children.item(ii)) return ii; } return -1; } EvoEditor.ClaimAffectedContent = function(startNode, endNode, flags) { var commonParent, startChild, endChild; var firstChildIndex = -1, html = "", ii; var withHtml = (flags & EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML) != 0; var currentElemsArray = null; if (!startNode) { startNode = document.getSelection().anchorNode; endNode = document.getSelection().focusNode; if (!startNode) { startNode = document.body; } } if (!endNode) { endNode = document.getSelection().focusNode; if (!endNode) endNode = startNode; } if ((flags & EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE) != 0) { if (startNode && !(startNode === document.body)) { startNode = startNode.parentElement; } while (startNode && !(startNode === document.body)) { if (EvoEditor.IsBlockNode(startNode)) { break; } startNode = startNode.parentElement; } } if (withHtml) { var node = startNode; // cannot store only part of the HTML in a TABLE, only whole, because restoring // for example only "text" into a floating element drops the if (!node.tagName) node = node.parentElement; if (node.tagName == "TH" || node.tagName == "TD" || node.tagName == "TR") { node = EvoEditor.getParentElement("TABLE", node, true); if (node) startNode = node; } currentElemsArray = EvoEditor.RemoveCurrentElementAttr(); } commonParent = EvoEditor.GetCommonParent(startNode, endNode, false); startChild = EvoEditor.GetDirectChild(commonParent, startNode); endChild = EvoEditor.GetDirectChild(commonParent, endNode); for (ii = 0 ; ii < commonParent.children.length; ii++) { var child = commonParent.children.item(ii); if (firstChildIndex == -1) { /* The selection can be made both from the top to the bottom and from the bottom to the top, thus cover both cases. */ if (child === startChild) { firstChildIndex = ii; } else if (child === endChild) { endChild = startChild; startChild = child; firstChildIndex = ii; } } if (firstChildIndex != -1) { if (withHtml) { html += child.outerHTML; } if (child === endChild) { ii++; break; } } } var affected = {}; affected.path = EvoSelection.GetChildPath(document.body, commonParent); affected.firstChildIndex = firstChildIndex; affected.restChildrenCount = commonParent.children.length - ii; if (withHtml) { if (firstChildIndex == -1) affected.html = commonParent.innerHTML; else affected.html = html; EvoEditor.RestoreCurrentElementAttr(currentElemsArray); } return affected; } /* Calls EvoEditor.ForeachChild() on a content described by 'affected', which is result of EvoEditor.ClaimAffectedContent(). */ EvoEditor.ForeachChildInAffectedContent = function(affected, traversar) { if (!affected || !traversar) { throw "EvoEditor.ForeachChildInAffectedContent: No 'affected' or 'traversar'"; } var parent, firstChildIndex, lastChildIndex; parent = EvoSelection.FindElementByPath(document.body, affected.path); if (!parent) { throw "EvoEditor.ForeachChildInAffectedContent: Cannot find parent"; } firstChildIndex = affected.firstChildIndex; /* Cannot subtract one, when none left, because the child index is inclusive */ lastChildIndex = parent.children.length - affected.restChildrenCount + (affected.restChildrenCount ? -1 : 0); return EvoEditor.ForeachChild(parent, firstChildIndex, lastChildIndex, traversar); } EvoEditor.EmitContentChanged = function() { if (window.webkit.messageHandlers.contentChanged) window.webkit.messageHandlers.contentChanged.postMessage(null); } EvoEditor.StoreSelection = function() { EvoEditor.storedSelection = EvoSelection.Store(document); } EvoEditor.RestoreSelection = function() { if (EvoEditor.storedSelection) { EvoSelection.Restore(document, EvoEditor.storedSelection); EvoEditor.storedSelection = null; } } EvoEditor.removeEmptyStyleAttribute = function(element) { if (element && !element.style.length) element.removeAttribute("style"); } EvoEditor.applySetAlignment = function(record, isUndo) { if (record.changes) { var ii, parent, child; parent = EvoSelection.FindElementByPath(document.body, record.path); if (!parent) { throw "EvoEditor.applySetAlignment: Cannot find parent at path " + record.path; } for (ii = 0; ii < record.changes.length; ii++) { var change = record.changes[isUndo ? (record.changes.length - ii - 1) : ii]; child = EvoSelection.FindElementByPath(parent, change.path); if (!child) { throw "EvoEditor.applySetAlignment: Cannot find child"; } if (isUndo) { child.style.textAlign = change.before; } else if ((record.applyValueAfter == "left" && child.style.direction != "rtl" && window.getComputedStyle(child).direction != "rtl") || (record.applyValueAfter == "right" && (child.style.direction == "rtl" || window.getComputedStyle(child).direction == "rtl"))) { child.style.textAlign = ""; } else { child.style.textAlign = record.applyValueAfter; } EvoEditor.removeEmptyStyleAttribute(child); } } } EvoEditor.SetAlignment = function(alignment) { var traversar = { record : null, toSet : null, anyChanged : false, flat : false, onlyBlockElements : true, exec : function(parent, element) { if (window.getComputedStyle(element, null).textAlign != traversar.toSet) { if (traversar.record) { if (!traversar.record.changes) traversar.record.changes = []; var change = {}; change.path = EvoSelection.GetChildPath(parent, element); change.before = element.style.textAlign; traversar.record.changes[traversar.record.changes.length] = change; } traversar.anyChanged = true; if ((traversar.toSet == "left" && element.style.direction != "rtl" && window.getComputedStyle(element).direction != "rtl") || (traversar.toSet == "right" && (element.style.direction == "rtl" || window.getComputedStyle(element).direction == "rtl"))) { element.style.textAlign = ""; } else { element.style.textAlign = traversar.toSet; } EvoEditor.removeEmptyStyleAttribute(element); } return true; } }; var affected = EvoEditor.ClaimAffectedContent(null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); switch (alignment) { case EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_NONE: traversar.toSet = ""; break; case EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_LEFT: traversar.toSet = "left"; break; case EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_CENTER: traversar.toSet = "center"; break; case EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_RIGHT: traversar.toSet = "right"; break; case EvoEditor.E_CONTENT_EDITOR_ALIGNMENT_JUSTIFY: traversar.toSet = "justify"; break; default: throw "EvoEditor.SetAlignment: Unknown alignment value: '" + alignment + "'"; } traversar.record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setAlignment", null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); try { EvoEditor.ForeachChildInAffectedContent(affected, traversar); if (traversar.record) { traversar.record.applyValueAfter = traversar.toSet; traversar.record.apply = EvoEditor.applySetAlignment; } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setAlignment"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); if (traversar.anyChanged) EvoEditor.EmitContentChanged(); } } EvoEditor.storeAttributes = function(element) { if (!element || !element.attributes.length) return null; var attributes = [], ii; for (ii = 0; ii < element.attributes.length; ii++) { var attr = { name : element.attributes.item(ii).name, value : element.attributes.item(ii).value }; attributes[attributes.length] = attr; } return attributes; } EvoEditor.restoreAttributes = function(element, attributes) { if (!element) return; while (element.attributes.length) { element.removeAttribute(element.attributes.item(element.attributes.length - 1).name); } if (!attributes) return; var ii; for (ii = 0; ii < attributes.length; ii++) { element.setAttribute(attributes[ii].name, attributes[ii].value); } } EvoEditor.storeElement = function(element) { if (!element) return null; var elementRecord = { tagName : element.tagName, attributes : EvoEditor.storeAttributes(element) }; return elementRecord; } EvoEditor.restoreElement = function(parentElement, beforeElement, tagName, attributes) { if (!parentElement) throw "EvoEditor.restoreElement: parentElement cannot be null"; if (!tagName) throw "EvoEditor.restoreElement: tagName cannot be null"; var node; node = parentElement.ownerDocument.createElement(tagName); EvoEditor.restoreAttributes(node, attributes); parentElement.insertBefore(node, beforeElement); return node; } EvoEditor.moveChildren = function(fromElement, toElement, beforeElement, prepareParent, selectionUpdater) { if (!fromElement) throw "EvoEditor.moveChildren: fromElement cannot be null"; if (beforeElement && toElement && !(beforeElement.parentElement === toElement)) throw "EvoEditor.moveChildren: beforeElement is not a direct child of toElement"; var node; for (node = toElement; node; node = node.parentElement) { if (node === fromElement) throw "EvoEditor.moveChildren: toElement cannot be child of fromElement"; } var firstElement = toElement; while (fromElement.firstChild) { if (prepareParent && fromElement.firstChild.tagName && fromElement.firstChild.tagName == "LI") { var toParent = prepareParent.exec(); if (toElement) { toElement.parentElement.insertBefore(toParent, toElement.nextElementSibling); } if (!firstElement) { firstElement = toParent; } var li = fromElement.firstChild, replacedBy = li.firstChild; while (li.firstChild) { toParent.append(li.firstChild); } if (selectionUpdater) selectionUpdater.beforeRemove(fromElement.firstChild); fromElement.removeChild(fromElement.firstChild); if (selectionUpdater) selectionUpdater.afterRemove(replacedBy); } else { if (!toElement && prepareParent) { toElement = prepareParent.exec(); // trying to move other than LI into UL/OL, thus do not enclose it into LI if (prepareParent.tagName == "LI" && (fromElement.tagName == "UL" || fromElement.tagName == "OL")) { var toParent = toElement.parentElement; toParent.removeChild(toElement); toElement = toParent; } } if (!firstElement) { firstElement = toElement; } toElement.insertBefore(fromElement.firstChild, beforeElement); } } return firstElement; } EvoEditor.renameElement = function(element, tagName, attributes, targetElement, selectionUpdater) { var prepareParent = { element : element, tagName : tagName, attributes : attributes, targetElement : targetElement, exec : function() { if (this.targetElement) return EvoEditor.restoreElement(this.targetElement, null, this.tagName, this.attributes); else return EvoEditor.restoreElement(this.element.parentElement, this.element, this.tagName, this.attributes); } }; var newElement; newElement = EvoEditor.moveChildren(element, null, null, prepareParent, selectionUpdater); element.remove(); return newElement; } EvoEditor.getBlockquoteLevel = function(node) { if (!node || node.tagName == "BODY") return 0; var blockquoteLevel = 0, parent = node; while (parent && parent.tagName != "BODY") { if (parent.tagName == "BLOCKQUOTE") blockquoteLevel++; parent = parent.parentElement; } return blockquoteLevel; } EvoEditor.SetBlockFormat = function(format) { var traversar = { toSet : null, createParent : null, firstLI : true, targetElement : null, selectionUpdater : null, flat : true, onlyBlockElements : true, exec : function(parent, element) { var newElement; if (this.toSet.tagName != "LI" && (element.tagName == "UL" || element.tagName == "OL")) { var affected = []; if (!EvoEditor.allChildrenInSelection(element, true, affected)) { var elemParent = element.parentElement, insBefore, jj; if (affected.length > 0 && !(affected[0] === element.firstElementChild)) { insBefore = EvoEditor.splitList(element, 1, affected); } else { insBefore = element; } for (jj = 0; jj < affected.length; jj++) { EvoEditor.insertListChildBefore(affected[jj], this.toSet.tagName, insBefore ? insBefore.parentElement : elemParent, insBefore, this.selectionUpdater); } if (!element.childElementCount) { this.selectionUpdater.beforeRemove(element); element.remove(); this.selectionUpdater.afterRemove(insBefore ? insBefore.previousElementSibling : elemParent.lastElementChild); } return true; } } if (this.firstLI) { if (this.createParent) { this.targetElement = EvoEditor.restoreElement(parent, element, this.createParent.tagName, this.createParent.attributes); } this.firstLI = false; } newElement = EvoEditor.renameElement(element, this.toSet.tagName, this.toSet.attributes, this.targetElement, this.selectionUpdater); if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT && (this.toSet.tagName == "DIV" || this.toSet.tagName == "PRE")) { var blockquoteLevel = EvoEditor.getBlockquoteLevel(newElement); if (blockquoteLevel > 0) { var width = -1; if (this.toSet.tagName == "DIV" && blockquoteLevel * 2 < EvoEditor.NORMAL_PARAGRAPH_WIDTH) width = EvoEditor.NORMAL_PARAGRAPH_WIDTH - blockquoteLevel * 2; EvoEditor.quoteParagraph(newElement, blockquoteLevel, width); } else if (this.toSet.tagName == "DIV") { newElement.setAttribute("style", "width: " + EvoEditor.NORMAL_PARAGRAPH_WIDTH + "ch;"); } } if (this.selectionUpdater) { this.selectionUpdater.beforeRemove(element); this.selectionUpdater.afterRemove(newElement); } return true; } }; traversar.selectionUpdater = EvoSelection.CreateUpdaterObject(); var affected = EvoEditor.ClaimAffectedContent(null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); switch (format) { case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH: traversar.toSet = { tagName : "DIV" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_PRE: traversar.toSet = { tagName : "PRE" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS: traversar.toSet = { tagName : "ADDRESS" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H1: traversar.toSet = { tagName : "H1" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H2: traversar.toSet = { tagName : "H2" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H3: traversar.toSet = { tagName : "H3" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H4: traversar.toSet = { tagName : "H4" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H5: traversar.toSet = { tagName : "H5" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_H6: traversar.toSet = { tagName : "H6" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_UNORDERED_LIST: traversar.toSet = { tagName : "LI" }; traversar.createParent = { tagName : "UL" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST: traversar.toSet = { tagName : "LI" }; traversar.createParent = { tagName : "OL" }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ROMAN: traversar.toSet = { tagName : "LI" }; traversar.createParent = { tagName : "OL", attributes : [ { name : "type", value : "I" } ] }; break; case EvoEditor.E_CONTENT_EDITOR_BLOCK_FORMAT_ORDERED_LIST_ALPHA: traversar.toSet = { tagName : "LI" }; traversar.createParent = { tagName : "OL", attributes : [ { name : "type", value : "A" } ] }; break; default: throw "EvoEditor.SetBlockFormat: Unknown block format value: '" + format + "'"; } EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBlockFormat", null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE | EvoEditor.CLAIM_CONTENT_FLAG_SAVE_HTML); try { EvoEditor.ForeachChildInAffectedContent(affected, traversar); traversar.selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBlockFormat"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.allChildrenInSelection = function(element, allowPartial, affected) { if (!element || !element.firstChild) return false; var selection = document.getSelection(), all; all = selection.containsNode(element.firstElementChild, allowPartial) && selection.containsNode(element.lastElementChild, allowPartial); var node; affected.length = 0; for (node = element.firstElementChild; node; node = node.nextElementSibling) { if (all || selection.containsNode(node, allowPartial)) affected[affected.length] = node; } return all; } EvoEditor.splitList = function(element, nParents, onlyAffected) { var parent, from = null; if (onlyAffected && onlyAffected.length) from = onlyAffected[onlyAffected.length - 1].nextElementSibling; if (!from) from = element.nextElementSibling; if (nParents == -1) { nParents = 0; for (parent = from; parent && parent.tagName != "BODY"; parent = parent.parentElement) { nParents++; } } var nextFrom, clone; parent = from ? from.parentElement : element.parentElement; if (!from && parent) { from = parent.nextElementSibling; nextFrom = from; nParents--; parent = parent.parentElement; } while (nParents > 0 && parent && parent.tagName != "HTML") { nParents--; nextFrom = null; if (from) { clone = from.parentElement.cloneNode(false); from.parentElement.parentElement.insertBefore(clone, from.parentElement.nextElementSibling); nextFrom = clone; while (from.nextElementSibling) { clone.appendChild(from.nextElementSibling); } clone.insertBefore(from, clone.firstElementChild); } from = nextFrom; parent = parent.parentElement; } if (nextFrom) return nextFrom; return parent.nextElementSibling; } EvoEditor.insertListChildBefore = function(child, tagName, parent, insBefore, selectionUpdater) { if (child.tagName == "LI") { var node = document.createElement(tagName); while(child.firstChild) node.appendChild(child.firstChild); parent.insertBefore(node, insBefore); if (selectionUpdater) selectionUpdater.beforeRemove(child); child.remove(); if (selectionUpdater) selectionUpdater.afterRemove(node); } else { parent.insertBefore(child, insBefore); } } EvoEditor.applyIndent = function(record, isUndo) { if (record.changes) { var ii, parent, child; parent = EvoSelection.FindElementByPath(document.body, record.path); if (!parent) { throw "EvoEditor.applyIndent: Cannot find parent at path " + record.path; } for (ii = 0; ii < record.changes.length; ii++) { var change = record.changes[isUndo ? (record.changes.length - ii - 1) : ii]; child = EvoSelection.FindElementByPath(change.pathIsFromBody ? document.body : parent, change.path); if (!child) { throw "EvoEditor.applyIndent: Cannot find child"; } if (change.isList) { EvoUndoRedo.RestoreChildren(change, child, isUndo); continue; } if (isUndo) { child.style.marginLeft = change.beforeMarginLeft; child.style.marginRight = change.beforeMarginRight; child.style.width = change.beforeWidth; } else { child.style.marginLeft = change.afterMarginLeft; child.style.marginRight = change.afterMarginRight; child.style.width = change.afterWidth; } EvoEditor.removeEmptyStyleAttribute(child); } } } EvoEditor.Indent = function(increment) { var traversar = { record : null, selectionUpdater : null, increment : increment, flat : true, onlyBlockElements : true, exec : function(parent, element) { var change = null, isList = element.tagName == "UL" || element.tagName == "OL"; var isNested = isList && (element.parentElement.tagName == "UL" || element.parentElement.tagName == "OL"); if (traversar.record) { if (!traversar.record.changes) traversar.record.changes = []; change = {}; change.pathIsFromBody = false; if (isList) { change.isList = isList; change.path = EvoSelection.GetChildPath(parent, element); } else { change.path = EvoSelection.GetChildPath(parent, element); change.beforeMarginLeft = element.style.marginLeft; change.beforeMarginRight = element.style.marginRight; change.beforeWidth = element.style.width; } traversar.record.changes[traversar.record.changes.length] = change; } if (isList) { var elemParent = null, all, affected = [], jj; all = EvoEditor.allChildrenInSelection(element, true, affected); if (this.increment) { var clone; clone = element.cloneNode(false); if (all) { if (change) { var childIndex = EvoEditor.GetChildIndex(element.parentElement, element); EvoUndoRedo.BackupChildrenBefore(change, element.parentElement, childIndex, childIndex); change.path = EvoSelection.GetChildPath(parent, element.parentElement); } element.parentElement.insertBefore(clone, element); clone.appendChild(element); if (change) EvoUndoRedo.BackupChildrenAfter(change, clone.parentElement); } else if (affected.length > 0) { if (change) { EvoUndoRedo.BackupChildrenBefore(change, element, EvoEditor.GetChildIndex(element, affected[0]), EvoEditor.GetChildIndex(element, affected[affected.length - 1])); } element.insertBefore(clone, affected[0]); for (jj = 0; jj < affected.length; jj++) { clone.appendChild(affected[jj]); } if (change) EvoUndoRedo.BackupChildrenAfter(change, element); } } else { var insBefore = null; elemParent = element.parentElement; // decrease indent in nested lists of the same type will merge items into one list if (isNested && elemParent.tagName == element.tagName && elemParent.getAttribute("type") == element.getAttribute("type")) { if (change) { var childIndex = EvoEditor.GetChildIndex(elemParent, element); EvoUndoRedo.BackupChildrenBefore(change, elemParent, childIndex, childIndex); change.path = EvoSelection.GetChildPath(parent, elemParent); } if (!all && affected.length > 0 && !(affected[0] === element.firstElementChild)) { insBefore = EvoEditor.splitList(element, 1, affected); } else { insBefore = element; } for (jj = 0; jj < affected.length; jj++) { elemParent.insertBefore(affected[jj], insBefore); } if (!element.childElementCount) { this.selectionUpdater.beforeRemove(element); element.remove(); this.selectionUpdater.afterRemove(affected[0]); } if (change) EvoUndoRedo.BackupChildrenAfter(change, elemParent); } else { var tmpElement = element; if (isNested) { tmpElement = elemParent; elemParent = elemParent.parentElement; } if (change) { var childIndex = EvoEditor.GetChildIndex(elemParent, tmpElement); EvoUndoRedo.BackupChildrenBefore(change, elemParent, childIndex, childIndex); if (isNested) { change.pathIsFromBody = true; change.path = EvoSelection.GetChildPath(document.body, elemParent); } else { change.path = EvoSelection.GetChildPath(parent, elemParent); } } if (isNested) { var clone; insBefore = EvoEditor.splitList(element, 1, affected); clone = element.cloneNode(false); if (insBefore) insBefore.parentElement.insertBefore(clone, insBefore); else elemParent.insertBefore(clone, insBefore); for (jj = 0; jj < affected.length; jj++) { clone.appendChild(affected[jj]); } } else { if (!all && affected.length > 0 && affected[affected.length - 1] === element.lastElementChild) { insBefore = element.nextElementSibling; } else if (!all && affected.length > 0 && !(affected[0] === element.firstElementChild)) { insBefore = EvoEditor.splitList(element, 1, affected); } else { insBefore = element; } for (jj = 0; jj < affected.length; jj++) { EvoEditor.insertListChildBefore(affected[jj], "DIV", insBefore ? insBefore.parentElement : elemParent, insBefore, this.selectionUpdater); } } while (element && !(element === elemParent) && !element.childElementCount) { tmpElement = element.parentElement; this.selectionUpdater.beforeRemove(element); element.remove(); this.selectionUpdater.afterRemove(insBefore ? insBefore.previousElementSibling : elemParent.lastElementChild); element = tmpElement; } if (change) EvoUndoRedo.BackupChildrenAfter(change, elemParent); } } } else { var currValue = null, dir, width; dir = window.getComputedStyle(element).direction; if (dir == "rtl") { if (element.style.marginRight.endsWith("ch")) currValue = element.style.marginRight; } else { // "ltr" or other if (element.style.marginLeft.endsWith("ch")) currValue = element.style.marginLeft; } if (!currValue) { currValue = 0; } else { currValue = parseInt(currValue.slice(0, -2)); if (!Number.isInteger(currValue)) currValue = 0; } width = 0; if (element.style.width.endsWith("ch")) { width = parseInt(element.style.width.slice(0, -2)); if (!Number.isInteger(width)) width = 0; } if (traversar.increment) { if (width && width - EvoEditor.TEXT_INDENT_SIZE > 0) width = width - EvoEditor.TEXT_INDENT_SIZE; currValue = (currValue + EvoEditor.TEXT_INDENT_SIZE) + "ch"; } else if (currValue > EvoEditor.TEXT_INDENT_SIZE) { if (width) width = width + EvoEditor.TEXT_INDENT_SIZE; currValue = (currValue - EvoEditor.TEXT_INDENT_SIZE) + "ch"; } else { if (width) width = width + currValue; currValue = ""; } if (dir == "rtl") { element.style.marginRight = currValue; } else { // "ltr" or other element.style.marginLeft = currValue; } if (width) element.style.width = width + "ch"; if (change) { change.afterMarginLeft = element.style.marginLeft; change.afterMarginRight = element.style.marginRight; change.afterWidth = element.style.width; } EvoEditor.removeEmptyStyleAttribute(element); } return true; } }; var affected = EvoEditor.ClaimAffectedContent(null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); traversar.record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, increment ? "Indent" : "Outdent", null, null, EvoEditor.CLAIM_CONTENT_FLAG_USE_PARENT_BLOCK_NODE); traversar.selectionUpdater = EvoSelection.CreateUpdaterObject(); try { EvoEditor.ForeachChildInAffectedContent(affected, traversar); if (traversar.record) { traversar.record.apply = EvoEditor.applyIndent; } traversar.selectionUpdater.restore(); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, increment ? "Indent" : "Outdent"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.applyDivNormalize = function(record, isUndo) { var element = EvoSelection.FindElementByPath(document.body, record.path); if (!element) throw "EvoEditor.applyDivNormalize: Path not found"; var value; if (isUndo) value = record.beforeValue; else value = record.afterValue; if (record.isWidthStyle) { element.style.width = value; EvoEditor.removeEmptyStyleAttribute(element); } else { element.innerHTML = value; } } EvoEditor.correctParagraphsAfterInsertContent = function(opType) { var node, list, ii; list = document.body.getElementsByTagName("DIV"); for (ii = 0; ii < list.length; ii++) { node = list[ii]; if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { var beforeValue = node.style.width; EvoEditor.maybeUpdateParagraphWidth(node); if (node.style.width != beforeValue) { var record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType + "::divWidths", node, node, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { if (record) { record.path = EvoSelection.GetChildPath(document.body, node); record.beforeValue = beforeValue; record.afterValue = node.style.width; record.isWidthStyle = true; record.apply = EvoEditor.applyDivNormalize; } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType + "::divWidths"); } } } if (!node.firstChild) { var record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType + "::divBR", node, node, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { beforeValue = node.innerHTML; node.appendChild(document.createElement("BR")); if (record) { record.path = EvoSelection.GetChildPath(document.body, node); record.beforeValue = beforeValue; record.afterValue = node.innerHTML; record.apply = EvoEditor.applyDivNormalize; } } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, opType + "::divBR"); } } } } EvoEditor.InsertHTML = function(opType, html) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); try { document.execCommand("insertHTML", false, html); var node, list, ii; list = document.body.getElementsByTagName("BLOCKQUOTE"); for (ii = 0; ii < list.length; ii++) { node = list[ii]; EvoEditor.setAttributeWithUndoRedo("InsertHTML::fixBlockquote", node, "class", null); EvoEditor.setAttributeWithUndoRedo("InsertHTML::fixBlockquote", node, "style", null); } EvoEditor.correctParagraphsAfterInsertContent(opType); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.InsertText = function(opType, text) { EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); try { document.execCommand("insertText", false, text); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_GROUP, opType); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_MAYBE); EvoEditor.EmitContentChanged(); } } EvoEditor.SetBodyAttribute = function(name, value) { EvoEditor.setAttributeWithUndoRedo("SetBodyAttribute", document.body, name, value); } EvoEditor.applySetBodyFontName = function(record, isUndo) { EvoEditor.UpdateStyleSheet("x-evo-body-fontname", isUndo ? record.beforeCSS : record.afterCSS); if (record.beforeStyle != record.afterStyle) { document.body.style.fontFamily = isUndo ? record.beforeStyle : record.afterStyle; EvoEditor.removeEmptyStyleAttribute(body.document); } } EvoEditor.SetBodyFontName = function(name) { var record; record = EvoUndoRedo.StartRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBodyFontName", document.body, document.body, EvoEditor.CLAIM_CONTENT_FLAG_NONE); try { var beforeCSS, css, beforeStyle; if (name) css = "body { font-family: " + name + "; }"; else css = null; beforeStyle = document.body.style.fontFamily; beforeCSS = EvoEditor.UpdateStyleSheet("x-evo-body-fontname", css); if (name != document.body.style.fontFamily) document.body.style.fontFamily = name ? name : ""; if (record) { record.apply = EvoEditor.applySetBodyFontName; record.beforeCSS = beforeCSS; record.afterCSS = css; record.beforeStyle = beforeStyle; record.afterStyle = document.body.style.fontFamily; if (record.beforeCSS == record.afterCSS && record.beforeStyle == record.afterStyle) record.ignore = true; } EvoEditor.removeEmptyStyleAttribute(document.body); } finally { EvoUndoRedo.StopRecord(EvoUndoRedo.RECORD_KIND_CUSTOM, "setBodyFontName"); EvoEditor.maybeUpdateFormattingState(EvoEditor.FORCE_YES); if (!record || !record.ignore) EvoEditor.EmitContentChanged(); } } EvoEditor.emptyParagraphAsHtml = function() { if (EvoEditor.mode == EvoEditor.MODE_PLAIN_TEXT) { return "

"; } else { return "

"; } } EvoEditor.initializeContent = function() { // for backward compatibility document.execCommand("StyleWithCSS", false, "false"); if (document.body) { // attach on body, thus it runs before EvoUndoRedo.beforeInputCb() document.body.onbeforeinput = EvoEditor.beforeInputCb; if (!document.body.firstChild) { EvoUndoRedo.Disable(); try { document.body.innerHTML = EvoEditor.emptyParagraphAsHtml(); } finally { EvoUndoRedo.Enable(); } } // make sure there is a selection if (!document.getSelection().anchorNode || document.getSelection().anchorNode.tagName == "HTML") { document.getSelection().setPosition(document.body.firstChild ? document.body.firstChild : document.body, 0); } } } EvoEditor.getNextNodeInHierarchy = function(node, upToNode) { if (!node) return null; var next; next = node.firstChild; if (!next) next = node.nextSibling; if (!next) { next = node.parentElement; if (next === upToNode || next === document.body) next = null; while (next) { if (next.nextSibling) { next = next.nextSibling; break; } else { next = next.parentElement; if (next === upToNode || next === document.body) next = null; } } } return next; } // it already knows the line is too long; the node is where the text length exceeded EvoEditor.quoteParagraphWrap = function(node, lineLength, wrapWidth, prefixHtml) { if (node.nodeType == node.ELEMENT_NODE) { var br = document.createElement("BR"); br.className = "-x-evo-wrap-br"; node.insertAdjacentElement("beforebegin", br); node.insertAdjacentHTML("beforebegin", prefixHtml); return node.innerText.length; } var words = node.nodeValue.split(" "), ii, offset = 0, inc; for (ii = 0; ii < words.length; ii++) { var word = words[ii], wordLen = word.length; if (lineLength + wordLen > wrapWidth) { if (offset > 0) { node.splitText(offset - 1); node = node.nextSibling; // erase the space at the end of the line node.splitText(1); var next = node.nextSibling; node.remove(); node = next; var br = document.createElement("BR"); br.className = "-x-evo-wrap-br"; node.parentElement.insertBefore(br, node); br.insertAdjacentHTML("afterend", prefixHtml); } offset = 0; lineLength = 0; } inc = wordLen + (ii + 1 < words.length ? 1 : 0); lineLength += inc; offset += inc; } return lineLength; } EvoEditor.getBlockquotePrefixHtml = function(blockquoteLevel) { var prefixHtml; prefixHtml = "> ".repeat(blockquoteLevel); prefixHtml = "" + prefixHtml + ""; return prefixHtml; } EvoEditor.quoteParagraph = function(paragraph, blockquoteLevel, wrapWidth) { if (!paragraph || !(blockquoteLevel > 0)) return; EvoEditor.removeQuoteMarks(paragraph); if (paragraph.tagName == "PRE") wrapWidth = -1; var node = paragraph.firstChild, next, lineLength = 0; var prefixHtml = EvoEditor.getBlockquotePrefixHtml(blockquoteLevel); while (node) { next = EvoEditor.getNextNodeInHierarchy(node, paragraph); if (node.nodeType == node.TEXT_NODE) { if (wrapWidth > 0 && lineLength + node.nodeValue.length > wrapWidth) { lineLength = EvoEditor.quoteParagraphWrap(node, lineLength, wrapWidth, prefixHtml); } else { lineLength += node.nodeValue.length; } } else if (node.nodeType == node.ELEMENT_NODE) { if (node.tagName == "BR") { if (node.classList.contains("-x-evo-wrap-br")) { node.remove(); } else { if (node.parentElement.childNodes.length != 1) node.insertAdjacentHTML("afterend", prefixHtml); lineLength = 0; } } else if (node.tagName == "A") { var len = node.innerText.length; if (wrapWidth > 0 && lineLength + len > wrapWidth && (!next || next.tagName != "BR")) { lineLength = EvoEditor.quoteParagraphWrap(node, lineLength, wrapWidth, prefixHtml); } else { lineLength += len; } // do not traverse into the anchor element next = node.nextSibling; } } node = next; } paragraph.insertAdjacentHTML("afterbegin", prefixHtml); } EvoEditor.reBlockquotePlainText = function(plainText, usePreTag) { var lines = plainText.replace(/\&/g, "&").split("\n"), ii, html = "", level = 0; for (ii = 0; ii < lines.length; ii++) { var line = lines[ii], newLevel = 0, skip = 0; // Conversion to Plain Text adds empty line at the end if (ii + 1 >= lines.length && !line[0]) break; while (line[skip] == '>') { newLevel++; skip++; if (line[skip] == ' ') skip++; } while (newLevel > level) { html += "
"; 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, ">") : "
"); html += usePreTag ? "
" : ""; } 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 += "
"; 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 without
inside 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();