text: /* Formatblock
   * Is used to insert block level elements 
   * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
   * 
  */
  (function(wysihtml) {
  
    var dom = wysihtml.dom,
        // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
        // instead of creating a H4 within a H1 which would result in semantically invalid html
        UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre",
        BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote",
        INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u";
  
    function correctOptionsForSimilarityCheck(options) {
      return {
        nodeName: options.nodeName || null,
        className: (!options.classRegExp) ? options.className || null : null,
        classRegExp: options.classRegExp || null,
        styleProperty: options.styleProperty || null
      };
    }
  
    function getRangeNode(node, offset) {
      if (node.nodeType === 3) {
        return node;
      } else {
        return node.childNodes[offset] || node;
      }
    }
  
    // Returns if node is a line break
    function isBr(n) {
      return n && n.nodeType === 1 && n.nodeName === "BR";
    }
  
    // Is block level element
    function isBlock(n, composer) {
      return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block";
    }
  
    // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
    function isBookmark(n) {
      return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
    }
  
    // Is line breaking node
    function isLineBreaking(n, composer) {
      return isBr(n) || isBlock(n, composer);
    }
  
    // Removes empty block level elements
    function cleanup(composer, newBlockElements) {
      wysihtml.dom.removeInvisibleSpaces(composer.element);
      var container = composer.element,
          allElements = container.querySelectorAll(BLOCK_ELEMENTS),
          noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '),
          uneditables = container.querySelectorAll(noEditQuery),
          elements = wysihtml.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents
          nbIdx;
  
      for (var i = elements.length; i--;) {
        if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) {
          // If cleanup removes some new block elements. remove them from newblocks array too
          nbIdx = wysihtml.lang.array(newBlockElements).indexOf(elements[i]);
          if (nbIdx > -1) {
            newBlockElements.splice(nbIdx, 1);
          }
          elements[i].parentNode.removeChild(elements[i]);
        }
      }
      
      return newBlockElements;
    }
  
    function defaultNodeName(composer) {
      return composer.config.useLineBreaks ? "DIV" : "P";
    }
  
    // The outermost un-nestable block element parent of from node
    function findOuterBlock(node, container, allBlocks) {
      var n = node,
          block = null;
          
      while (n && container && n !== container) {
        if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) {
          block = n;
        }
        n = n.parentNode;
      }
  
      return block;
    }
  
    // Clone for splitting the inner inline element out of its parent inline elements context
    // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return
    function cloneOuterInlines(node, container) {
      var n = node,
          innerNode,
          parentNode,
          el = null,
          el2;
  
      while (n && container && n !== container) {
        if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) {
          parentNode = n;
          if (el === null) {
            el = n.cloneNode(false);
            innerNode = el;
          } else {
            el2 = n.cloneNode(false);
            el2.appendChild(el);
            el = el2;
          }
        }
        n = n.parentNode;
      }
  
      return {
        parent: parentNode,
        outerNode: el,
        innerNode: innerNode
      };
    }
  
    // Formats an element according to options nodeName, className, styleProperty, styleValue
    // If element is not defined, creates new element
    // if opotions is null, remove format instead
    function applyOptionsToElement(element, options, composer) {
  
      if (!element) {
        element = composer.doc.createElement(options.nodeName || defaultNodeName(composer));
        // Add invisible space as otherwise webkit cannot set selection or range to it correctly
        element.appendChild(composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE));
      }
  
      if (options.nodeName && element.nodeName !== options.nodeName) {
        element = dom.renameElement(element, options.nodeName);
      }
  
      // Remove similar classes before applying className
      if (options.classRegExp) {
        element.className = element.className.replace(options.classRegExp, "");
      }
      if (options.className) {
        element.classList.add(options.className);
      }
  
      if (options.styleProperty && typeof options.styleValue !== "undefined") {
        element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
      }
  
      return element;
    }
  
    // Unsets element properties by options
    // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes)
    function removeOptionsFromElement(element, options, composer) {
      var style, classes,
          prevNode = element.previousSibling,
          nextNode = element.nextSibling,
          unwrapped = false;
  
      if (options.styleProperty) {
        element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = '';
      }
      if (options.className) {
        element.classList.remove(options.className);
      }
  
      if (options.classRegExp) {
        element.className = element.className.replace(options.classRegExp, "");
      }
  
      // Clean up blank class attribute
      if (element.getAttribute('class') !== null && element.getAttribute('class').trim() === "") {
        element.removeAttribute('class');
      }
  
      if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) {
        style = element.getAttribute('style');
        if (!style || style.trim() === '') {
          dom.unwrap(element);
          unwrapped = true;
        } else {
          element = dom.renameElement(element, defaultNodeName(composer));
        }
      }
  
      // Clean up blank style attribute
      if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") {
        element.removeAttribute('style');
      }
  
      if (unwrapped) {
        applySurroundingLineBreaks(prevNode, nextNode, composer);
      }
    }
  
    // Unwraps block level elements from inside content
    // Useful as not all block level elements can contain other block-levels
    function unwrapBlocksFromContent(element) {
      var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents
          nextEl, prevEl;
  
      for (var i = blocks.length; i--;) {
        nextEl = wysihtml.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
        prevEl = wysihtml.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
        
        if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
          if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
            blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
          }
        }
        if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
          if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
            blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
          }
        }
        wysihtml.dom.unwrap(blocks[i]);
      }
    }
  
    // Fix ranges that visually cover whole block element to actually cover the block
    function fixRangeCoverage(range, composer) {
      var node,
          start = range.startContainer,
          end = range.endContainer;
  
      // If range has only one childNode and it is end to end the range, extend the range to contain the container element too
      // This ensures the wrapper node is modified and optios added to it
      if (start && start.nodeType === 1 && start === end) {
        if (start.firstChild === start.lastChild && range.endOffset === 1) {
          if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
            range.setStartBefore(start);
            range.setEndAfter(end);
          }
        }
        return;
      }
  
      // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too
      if (start && start.nodeType === 1 && end.nodeType === 3) {
        if (start.firstChild === end && range.endOffset === end.data.length) {
          if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
            range.setEndAfter(start);
          }
        }
        return;
      }
      
      // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too
      if (end && end.nodeType === 1 && start.nodeType === 3) {
        if (end.firstChild === start && range.startOffset === 0) {
          if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') {
            range.setStartBefore(end);
          }
        }
        return;
      }
  
      // If range covers a whole textnode and the textnode is the only child of node, extend range to node 
      if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) {
        if (range.endOffset == end.data.length && range.startOffset === 0) {
          node = start.parentNode;
          if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') {
            range.setStartBefore(node);
            range.setEndAfter(node);
          }
        }
        return;
      }
    }
    
    // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges
    // Some places do not allow block level elements inbetween (inside ul and outside li)
    // TODO: might need extending for other nodes besides li (maybe dd,dl,dt)
    function fixNotPermittedInsertionPoints(ranges) {
      var newRanges = [],
          lis, j, maxj, tmpRange, rangePos, closestLI;
          
      for (var i = 0, maxi = ranges.length; i < maxi; i++) {
        
        // Fixes range start and end positions if inside UL or OL element (outside of LI)
        if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) {
          ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0);
        }
        if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) {
          closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)];
          if (closestLI.childNodes) {
            ranges[i].setEnd(closestLI, closestLI.childNodes.length);
          }
        }
  
        // Get all LI eleemnts in selection (fully or partially covered)
        // And make sure ranges are either inside LI or outside UL/OL
        // Split and add new ranges as needed to cover same range content
        // TODO: Needs improvement to accept DL, DD, DT
        lis = ranges[i].getNodes([1], function(node) {
          return node.nodeName === "LI";
        });
        if (lis.length > 0) {
        
          for (j = 0, maxj = lis.length; j < maxj; j++) {
            rangePos = ranges[i].compareNode(lis[j]);
  
            // Fixes start of range that crosses LI border
            if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) {
              // Range starts before and ends inside the node
  
              tmpRange = ranges[i].cloneRange();
              closestLI = wysihtml.dom.domNode(lis[j]).prev({nodeTypes: [1]});
              
              if (closestLI) {
                tmpRange.setEnd(closestLI, closestLI.childNodes.length);
              } else if (lis[j].closest('ul, ol')) {
                tmpRange.setEndBefore(lis[j].closest('ul, ol'));
              } else {
                tmpRange.setEndBefore(lis[j]);
              }
              newRanges.push(tmpRange);
              ranges[i].setStart(lis[j], 0);
            }
            
            // Fixes end of range that crosses li border
            if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) {
              // Range starts inside the node and ends after node
              
              tmpRange = ranges[i].cloneRange();
              tmpRange.setEnd(lis[j], lis[j].childNodes.length);
              newRanges.push(tmpRange);
              
              // Find next LI in list and if present set range to it, else 
              closestLI = wysihtml.dom.domNode(lis[j]).next({nodeTypes: [1]});
              if (closestLI) {
                ranges[i].setStart(closestLI, 0);
              } else if (lis[j].closest('ul, ol')) {
                ranges[i].setStartAfter(lis[j].closest('ul, ol'));
              } else {
                ranges[i].setStartAfter(lis[j]);
              } 
            }
          }
          newRanges.push(ranges[i]);
        } else {
          newRanges.push(ranges[i]);
        }
      }
      return newRanges;
    }
    
    // Return options object with nodeName set if original did not have any
    // Node name is set to local or global default
    function getOptionsWithNodename(options, defaultName, composer) {
      var correctedOptions = (options) ? wysihtml.lang.object(options).clone(true) : null;
      if (correctedOptions) {  
        correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer);
      }
      return correctedOptions;
    }
    
    // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted
    // Also wraps empty clones of split parent tags around fragment to keep formatting
    // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not)
    function injectFragmentToRange(fragment, range, composer, firstOuterBlock) {
      var rangeStartContainer = range.startContainer,
          firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true),
          outerInlines, first, last, prev, next;
      
      if (firstOuterBlock) {
        // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
        first = fragment.firstChild;
        last = fragment.lastChild;
  
        composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
  
        next = wysihtml.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true});
        prev = wysihtml.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
  
        if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) {
          first.parentNode.insertBefore(composer.doc.createElement('br'), first);
        }
  
        if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) {
          next.parentNode.insertBefore(composer.doc.createElement('br'), next);
        }
  
      } else {
        // Ensure node does not get inserted into an inline where it is not allowed
        outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
        if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
          if (fragment.childNodes.length === 1) {
            while(fragment.firstChild.firstChild) {
              outerInlines.innerNode.appendChild(fragment.firstChild.firstChild);
            }
            fragment.firstChild.appendChild(outerInlines.outerNode);
          }
          composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
        } else {
          var fc = fragment.firstChild,
              lc = fragment.lastChild;
  
          range.insertNode(fragment);
          // restore range position as it might get lost in webkit sometimes
          range.setStartBefore(fc);
          range.setEndAfter(lc);
        }
      }
    }
    
    // Removes all block formatting from range
    function clearRangeBlockFromating(range, closestBlockName, composer) {
      var r = range.cloneRange(),
          prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling,
          nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling,
          content = r.extractContents(),
          fragment = composer.doc.createDocumentFragment(),
          children, blocks,
          first = true;
          
      while(content.firstChild) {
        // Iterate over all selection content first level childNodes
        if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
          // If node is a block element
          // Split block formating and add new block to wrap caret
          
          unwrapBlocksFromContent(content.firstChild);
          children = wysihtml.dom.unwrap(content.firstChild);
          
          // Add line break before if needed
          if (children.length > 0) {
            if (
              (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) ||
              (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer)))
            ){
              fragment.appendChild(composer.doc.createElement('BR'));
            }
          }
          
          for (var c = 0, cmax = children.length; c < cmax; c++) {
            fragment.appendChild(children[c]);
          }
          
          // Add line break after if needed
          if (children.length > 0) {
            if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) {
              if (nextNode || fragment.lastChild !== content.lastChild) {
                fragment.appendChild(composer.doc.createElement('BR'));
              }
            }
          }
          
        } else {
          fragment.appendChild(content.firstChild);
        }
        
        first = false;
      }
      blocks = wysihtml.lang.array(fragment.childNodes).get();
      injectFragmentToRange(fragment, r, composer);
      return blocks;
    }
    
    // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself)
    function removeSurroundingLineBreaks(prevNode, nextNode, composer) {
      var prevPrev = prevNode && wysihtml.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
      if (isBr(nextNode)) {
        nextNode.parentNode.removeChild(nextNode);
      }
      if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) {
        prevNode.parentNode.removeChild(prevNode);
      }
    }
  
    function applySurroundingLineBreaks(prevNode, nextNode, composer) {
      var prevPrev;
  
      if (prevNode && isBookmark(prevNode)) {
        prevNode = prevNode.previousSibling;
      }
      if (nextNode && isBookmark(nextNode)) {
        nextNode = nextNode.nextSibling;
      }
  
      prevPrev = prevNode && prevNode.previousSibling;
  
      if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) {
        prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling);
      }
  
      if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) {
        nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode);
      }
    }
  
    var isWhitespaceBefore = function (textNode, offset) {
      var str = textNode.data ? textNode.data.slice(0, offset) : "";
      return (/^\s*$/).test(str);
    }
  
    var isWhitespaceAfter = function (textNode, offset) {
      var str = textNode.data ? textNode.data.slice(offset) : "";
      return (/^\s*$/).test(str);
    }
  
    var trimBlankTextsAndBreaks = function(fragment) {
      if (fragment) {
        while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) {
          fragment.removeChild(fragment.firstChild);
        }
  
        while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) {
          fragment.removeChild(fragment.lastChild);
        }
  
        if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
          fragment.removeChild(fragment.firstChild);
        }
  
        if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
          fragment.removeChild(fragment.lastChild);
        }
      }
    }
  
    // Wrap the range with a block level element
    // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
    function wrapRangeWithElement(range, options, closestBlockName, composer) {
      var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
          r = range.cloneRange(),
          rangeStartContainer = r.startContainer,
          startNode = getRangeNode(r.startContainer, r.startOffset),
          endNode = getRangeNode(r.endContainer, r.endOffset),
          prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode :  wysihtml.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
          nextNode = (
            (
              r.endContainer.nodeType === 1 &&
              r.endContainer.childNodes[r.endOffset] === endNode &&
              (
                endNode.nodeType === 1 ||
                !isWhitespaceAfter(endNode, r.endOffset) &&
                !wysihtml.dom.domNode(endNode).is.rangyBookmark()
              )
            ) || (
              r.endContainer === endNode &&
              endNode.nodeType === 3 &&
              !isWhitespaceAfter(endNode, r.endOffset)
            )
          ) ? endNode : wysihtml.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
          content = r.extractContents(),
          fragment = composer.doc.createDocumentFragment(),
          similarOuterBlock = similarOptions ? wysihtml.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
          splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
          firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
          wrapper, blocks, children,
          firstc, lastC;
  
      if (wysihtml.dom.domNode(nextNode).is.rangyBookmark()) {
        endNode = nextNode;
        nextNode = endNode.nextSibling;
      }
  
      trimBlankTextsAndBreaks(content);
  
      if (options && options.nodeName === "BLOCKQUOTE") {
        
        // If blockquote is to be inserted no quessing just add it as outermost block on line or selection
        var tmpEl = applyOptionsToElement(null, options, composer);
        tmpEl.appendChild(content);
        fragment.appendChild(tmpEl);
        blocks = [tmpEl];
        
      } else {
  
        if (!content.firstChild) {
          // IF selection is caret (can happen if line is empty) add format around tag 
          fragment.appendChild(applyOptionsToElement(null, options, composer));
        } else {
  
          while(content.firstChild) {
            // Iterate over all selection content first level childNodes
            
            if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
              
              // If node is a block element
              // Escape(split) block formatting at caret
              applyOptionsToElement(content.firstChild, options, composer);
              if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
                unwrapBlocksFromContent(content.firstChild);
              }
              fragment.appendChild(content.firstChild);
              
            } else {
              
              // Wrap subsequent non-block nodes inside new block element
              wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer);
              while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
                if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
                  unwrapBlocksFromContent(content.firstChild);
                }
                wrapper.appendChild(content.firstChild);
              }
              fragment.appendChild(wrapper);
            }
          }
        }
  
        blocks = wysihtml.lang.array(fragment.childNodes).get();
      }
      injectFragmentToRange(fragment, r, composer, firstOuterBlock);
      removeSurroundingLineBreaks(prevNode, nextNode, composer);
  
      // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block
      // (if it contains rangy bookmark, so selection can be restored later correctly)
      if (blocks.length > 0 &&
        (
          typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark()
        )
      ) {
        blocks[blocks.length - 1].appendChild(composer.doc.createElement('br'));
      }
      return blocks;
    }
  
    // Find closest block level element
    function getParentBlockNodeName(element, composer) {
      var parentNode = wysihtml.dom.getParentElement(element, {
            query: BLOCK_ELEMENTS
          }, null, composer.element);
  
      return (parentNode) ? parentNode.nodeName : null;
    }
    
    // Expands caret to cover the closest block that:
    //   * cannot contain other block level elements (h1-6,p, etc)
    //   * Has the same nodeName that is to be inserted
    //   * has insertingNodeName
    //   * is DIV if insertingNodeName is not present
    //
    // If nothing found selects the current line
    function expandCaretToBlock(composer, insertingNodeName) {
      var parent = wysihtml.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
            query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'),
          }, null, composer.element),
          range;
  
      if (parent) {
        range = composer.selection.createRange();
        range.selectNode(parent);
        composer.selection.setSelection(range);
      } else if (!composer.isEmpty()) {
        composer.selection.selectLine();
      }
    }
    
    // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element
    // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
    function selectElements(newBlockElements, composer) {
      var range = composer.selection.createRange(),
          lastEl = newBlockElements[newBlockElements.length - 1],
          lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 :  lastEl.length || 0;
  
      range.setStart(newBlockElements[0], 0);
      range.setEnd(lastEl, lastOffset);
      range.select();
    }
    
    // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each
    // Return created/modified block level elements 
    // Method can be either "apply" or "remove"
    function formatSelection(method, composer, options) {
      var ranges = composer.selection.getOwnRanges(),
          newBlockElements = [],
          closestBlockName;
          
      // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th)
      ranges = fixNotPermittedInsertionPoints(ranges);
          
      for (var i = ranges.length; i--;) {
        fixRangeCoverage(ranges[i], composer);
        closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer);
        if (method === "remove") {
          newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer));
        } else {
          newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer));
        }
      }
      return newBlockElements;
    }
    
    // If properties is passed as a string, look for tag with that tagName/query 
    function parseOptions(options) {
      if (typeof options === "string") {
        options = {
          nodeName: options.toUpperCase()
        };
      }
      return options;
    }
  
    function caretIsOnEmptyLine(composer) {
      var caretInfo;
      if (composer.selection.isCollapsed()) {
        caretInfo = composer.selection.getNodesNearCaret();
        if (caretInfo && caretInfo.caretNode) {
          if (
            // caret is allready breaknode
            wysihtml.dom.domNode(caretInfo.caretNode).is.lineBreak() ||
            // caret is textnode
            (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak())) ||
            // Caret is temprorary rangy selection marker
            (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') &&
              (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.prevNode).is.block()) &&
              (!caretInfo.nextNode || wysihtml.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.nextNode).is.block())
            )
          ) {
            return true;
          }
        }
      }
      return false;
    }
  
    wysihtml.commands.formatBlock = {
      exec: function(composer, command, options) {
        options = parseOptions(options);
  
        // SW-2877 select font size class from the selected range text
        // Set it to text in clean editor
        var ranges = composer.selection.getOwnRanges(),
          range,
          textContainer,
          outerClass;
        if (ranges.length === 1) {
          range = ranges[0];
          textContainer = range.startContainer;
          
          while(textContainer && textContainer.tagName !== "SPAN" && textContainer.tagName != "P"){
            textContainer = textContainer.parentNode;
          }
          if (textContainer && textContainer.tagName === "SPAN"){
            outerClass = textContainer.className;
          }
          
        }
        
        var newBlockElements = [], bookmark, state, closestBlockName;
  
        // Find if current format state is active if options.toggle is set as true
        // In toggle case active state elemets are formatted instead of working directly on selection
        if (options && options.toggle) {
          state = this.state(composer, command, options);
        }
        if (state) {
          // Remove format from state nodes if toggle set and state on and selection is collapsed
          bookmark = rangy.saveSelection(composer.win);
          for (var j = 0, jmax = state.length; j < jmax; j++) {
            removeOptionsFromElement(state[j], options, composer);
          }
  
        } else {
          // If selection is caret expand it to cover nearest suitable block element or row if none found
          if (composer.selection.isCollapsed()) {
            bookmark = rangy.saveSelection(composer.win);
            expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
          }
          if (options) {
            newBlockElements = formatSelection("apply", composer, options);
          } else {
            // Options == null means block formatting should be removed from selection
            newBlockElements = formatSelection("remove", composer);
          }
  
        }
  
        // Remove empty block elements that may be left behind
        // Also remove them from new blocks list
        newBlockElements = cleanup(composer, newBlockElements);
        
        // Restore selection
        if (bookmark) {
          rangy.restoreSelection(bookmark);
        } else {
          selectElements(newBlockElements, composer);
        }
        wysihtml.commands.formatInline.cleanEditor(composer, outerClass);
      },
      
      // Removes all block formatting from selection
      remove: function(composer, command, options) {
        options = parseOptions(options);
        var newBlockElements, bookmark;
        
        // If selection is caret expand it to cover nearest suitable block element or row if none found
        if (composer.selection.isCollapsed()) {
          bookmark = rangy.saveSelection(composer.win);
          expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
        }
        
        newBlockElements = formatSelection("remove", composer);
        newBlockElements = cleanup(composer, newBlockElements);
        
        // Restore selection
        if (bookmark) {
          rangy.restoreSelection(bookmark);
        } else {
          selectElements(newBlockElements, composer);
        }
      },
  
      // If options as null is passed returns status describing all block level elements
      state: function(composer, command, options) {
        options = parseOptions(options);
  
        var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
              return wysihtml.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS });
            }).bind(this)),
            parentNodes = composer.selection.getSelectedOwnNodes(),
            parent;
  
        // Finds matching elements that are parents of selection and adds to nodes list
        for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
          parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element);
          if (parent && nodes.indexOf(parent) === -1) {
            nodes.push(parent);
          }
        }
  
        return (nodes.length === 0) ? false : nodes;
      }
  
    };
  })(wysihtml);
  