text: (function () {
      'use strict';
  
      var Toolbar = MediumEditor.Extension.extend({
          name: 'toolbar',
  
          /* Toolbar Options */
  
          /* align: ['left'|'center'|'right']
           * When the __static__ option is true, this aligns the static toolbar
           * relative to the medium-editor element.
           */
          align: 'center',
  
          /* allowMultiParagraphSelection: [boolean]
           * enables/disables whether the toolbar should be displayed when
           * selecting multiple paragraphs/block elements
           */
          allowMultiParagraphSelection: true,
  
          /* buttons: [Array]
           * the names of the set of buttons to display on the toolbar.
           */
          buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'],
  
          /* diffLeft: [Number]
           * value in pixels to be added to the X axis positioning of the toolbar.
           */
          diffLeft: 0,
  
          /* diffTop: [Number]
           * value in pixels to be added to the Y axis positioning of the toolbar.
           */
          diffTop: -10,
  
          /* firstButtonClass: [string]
           * CSS class added to the first button in the toolbar.
           */
          firstButtonClass: 'medium-editor-button-first',
  
          /* lastButtonClass: [string]
           * CSS class added to the last button in the toolbar.
           */
          lastButtonClass: 'medium-editor-button-last',
  
          /* standardizeSelectionStart: [boolean]
           * enables/disables standardizing how the beginning of a range is decided
           * between browsers whenever the selected text is analyzed for updating toolbar buttons status.
           */
          standardizeSelectionStart: false,
  
          /* static: [boolean]
           * enable/disable the toolbar always displaying in the same location
           * relative to the medium-editor element.
           */
          static: false,
  
          /* sticky: [boolean]
           * When the __static__ option is true, this enables/disables the toolbar
           * "sticking" to the viewport and staying visible on the screen while
           * the page scrolls.
           */
          sticky: false,
  
          /* stickyTopOffset: [Number]
           * Value in pixel of the top offset above the toolbar
           */
          stickyTopOffset: 0,
  
          /* updateOnEmptySelection: [boolean]
           * When the __static__ option is true, this enables/disables updating
           * the state of the toolbar buttons even when the selection is collapsed
           * (there is no selection, just a cursor).
           */
          updateOnEmptySelection: false,
  
          /* relativeContainer: [node]
           * appending the toolbar to a given node instead of body
           */
          relativeContainer: null,
  
          init: function () {
              MediumEditor.Extension.prototype.init.apply(this, arguments);
  
              this.initThrottledMethods();
  
              if (!this.relativeContainer) {
                  this.getEditorOption('elementsContainer').appendChild(this.getToolbarElement());
              } else {
                  this.relativeContainer.appendChild(this.getToolbarElement());
              }
          },
  
          // Helper method to execute method for every extension, but ignoring the toolbar extension
          forEachExtension: function (iterator, context) {
              return this.base.extensions.forEach(function (command) {
                  if (command === this) {
                      return;
                  }
                  return iterator.apply(context || this, arguments);
              }, this);
          },
  
          // Toolbar creation/deletion
  
          createToolbar: function () {
              var toolbar = this.document.createElement('div');
  
              toolbar.id = 'medium-editor-toolbar-' + this.getEditorId();
              toolbar.className = 'medium-editor-toolbar';
  
              if (this.static) {
                  toolbar.className += ' static-toolbar';
              } else if (this.relativeContainer) {
                  toolbar.className += ' medium-editor-relative-toolbar';
              } else {
                  toolbar.className += ' medium-editor-stalker-toolbar';
              }
  
              this.createToolbarButtons().forEach(function (row) {
                  toolbar.appendChild(row);
              });
  
              // Add any forms that extensions may have
              this.forEachExtension(function (extension) {
                  if (extension.hasForm) {
                      toolbar.appendChild(extension.getForm());
                  }
              });
  
              this.attachEventHandlers();
  
              return toolbar;
          },
  
          createToolbarButtons: function () {
              var rows = [];
  
              if (Array.isArray(this.buttons[0])) {
                  this.buttons.forEach(function (row, index) {
                      rows.push(this.createToolbarButtonsRow(row, index));
                  }, this);
              } else {
                  rows.push(this.createToolbarButtonsRow(this.buttons, 0));
              }
  
              return rows;
          },
  
          createToolbarButtonsRow: function (buttons, index) {
              var ul = this.document.createElement('ul'),
                  li,
                  btn,
                  buttonEls,
                  extension,
                  buttonName,
                  buttonOpts;
  
              ul.id = 'medium-editor-toolbar-actions' + this.getEditorId();
              if (index > 0) {
                  // Preserve backward compatibility and add index only for extra rows
                  ul.id += '-' + index;
              }
              ul.className = 'medium-editor-toolbar-actions';
              ul.style.display = 'block';
  
              buttons.forEach(function (button) {
                  if (typeof button === 'string') {
                      buttonName = button;
                      buttonOpts = null;
                  } else {
                      buttonName = button.name;
                      buttonOpts = button;
                  }
  
                  // If the button already exists as an extension, it'll be returned
                  // othwerise it'll create the default built-in button
                  extension = this.base.addBuiltInExtension(buttonName, buttonOpts);
  
                  if (extension && typeof extension.getButton === 'function') {
                      btn = extension.getButton(this.base);
                      li = this.document.createElement('li');
                      if (MediumEditor.util.isElement(btn)) {
                          li.appendChild(btn);
                      } else {
                          li.innerHTML = btn;
                      }
                      ul.appendChild(li);
                  }
              }, this);
  
              buttonEls = ul.querySelectorAll('button');
              if (buttons.length > 0) {
                  buttonEls[0].classList.add(this.firstButtonClass);
                  buttonEls[buttonEls.length - 1].classList.add(this.lastButtonClass);
              }
  
              return ul;
          },
  
          destroy: function () {
              if (this.toolbar) {
                  if (this.toolbar.parentNode) {
                      this.toolbar.parentNode.removeChild(this.toolbar);
                  }
                  delete this.toolbar;
              }
          },
  
          // Toolbar accessors
  
          getInteractionElements: function () {
              return this.getToolbarElement();
          },
  
          getToolbarElement: function () {
              if (!this.toolbar) {
                  this.toolbar = this.createToolbar();
              }
  
              return this.toolbar;
          },
  
          getToolbarActionsElement: function () {
              // For backward compatibility
              return this.getToolbarActionsElements()[0];
          },
  
          getToolbarActionsElements: function () {
              var els = this.getToolbarElement().querySelectorAll('.medium-editor-toolbar-actions');
              return Array.prototype.slice.call(els);
          },
  
          // Toolbar event handlers
  
          initThrottledMethods: function () {
              // throttledPositionToolbar is throttled because:
              // - It will be called when the browser is resizing, which can fire many times very quickly
              // - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
              this.throttledPositionToolbar = MediumEditor.util.throttle(function () {
                  if (this.base.isActive) {
                      this.positionToolbarIfShown();
                  }
              }.bind(this));
          },
  
          attachEventHandlers: function () {
              // MediumEditor custom events for when user beings and ends interaction with a contenteditable and its elements
              this.subscribe('blur', this.handleBlur.bind(this));
              this.subscribe('focus', this.handleFocus.bind(this));
  
              // Updating the state of the toolbar as things change
              this.subscribe('editableClick', this.handleEditableClick.bind(this));
              this.subscribe('editableKeyup', this.handleEditableKeyup.bind(this));
  
              // Handle mouseup on document for updating the selection in the toolbar
              this.on(this.document.documentElement, 'mouseup', this.handleDocumentMouseup.bind(this));
  
              // Add a scroll event for sticky toolbar
              if (this.static && this.sticky) {
                  // On scroll (capture), re-position the toolbar
                  this.on(this.window, 'scroll', this.handleWindowScroll.bind(this), true);
              }
  
              // On resize, re-position the toolbar
              this.on(this.window, 'resize', this.handleWindowResize.bind(this));
          },
  
          handleWindowScroll: function () {
              this.positionToolbarIfShown();
          },
  
          handleWindowResize: function () {
              this.throttledPositionToolbar();
          },
  
          handleDocumentMouseup: function (event) {
              // Do not trigger checkState when mouseup fires over the toolbar
              if (event &&
                      event.target &&
                      MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) {
                  return false;
              }
              this.checkState();
          },
  
          handleEditableClick: function () {
              // Delay the call to checkState to handle bug where selection is empty
              // immediately after clicking inside a pre-existing selection
              setTimeout(function () {
                  this.checkState();
              }.bind(this), 0);
          },
  
          handleEditableKeyup: function () {
              this.checkState();
          },
  
          handleBlur: function () {
              // Kill any previously delayed calls to hide the toolbar
              clearTimeout(this.hideTimeout);
  
              // Blur may fire even if we have a selection, so we want to prevent any delayed showToolbar
              // calls from happening in this specific case
              clearTimeout(this.delayShowTimeout);
  
              // Delay the call to hideToolbar to handle bug with multiple editors on the page at once
              this.hideTimeout = setTimeout(function () {
                  this.hideToolbar();
              }.bind(this), 1);
          },
  
          handleFocus: function () {
              this.checkState();
          },
  
          // Hiding/showing toolbar
  
          isDisplayed: function () {
              return this.getToolbarElement().classList.contains('medium-editor-toolbar-active');
          },
  
          showToolbar: function () {
              clearTimeout(this.hideTimeout);
              if (!this.isDisplayed()) {
                  this.getToolbarElement().classList.add('medium-editor-toolbar-active');
                  this.trigger('showToolbar', {}, this.base.getFocusedElement());
              }
          },
  
          hideToolbar: function () {
              if (this.isDisplayed()) {
                  this.getToolbarElement().classList.remove('medium-editor-toolbar-active');
                  this.trigger('hideToolbar', {}, this.base.getFocusedElement());
              }
          },
  
          isToolbarDefaultActionsDisplayed: function () {
              return this.getToolbarActionsElement().style.display === 'block';
          },
  
          hideToolbarDefaultActions: function () {
              if (this.isToolbarDefaultActionsDisplayed()) {
                  this.getToolbarActionsElements().forEach(function (el) {
                      el.style.display = 'none';
                  });
              }
          },
  
          showToolbarDefaultActions: function () {
              this.hideExtensionForms();
  
              if (!this.isToolbarDefaultActionsDisplayed()) {
                  this.getToolbarActionsElements().forEach(function (el) {
                      el.style.display = 'block';
                  });
              }
  
              // Using setTimeout + options.delay because:
              // We will actually be displaying the toolbar, which should be controlled by options.delay
              this.delayShowTimeout = this.base.delay(function () {
                  this.showToolbar();
              }.bind(this));
          },
  
          hideExtensionForms: function () {
              // Hide all extension forms
              this.forEachExtension(function (extension) {
                  if (extension.hasForm && extension.isDisplayed()) {
                      extension.hideForm();
                  }
              });
          },
  
          // Responding to changes in user selection
  
          // Checks for existance of multiple block elements in the current selection
          multipleBlockElementsSelected: function () {
              var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex
                  regexBlockElements = new RegExp('<(' + MediumEditor.util.blockContainerElementNames.join('|') + ')[^>]*>', 'g'),
                  selectionHTML = MediumEditor.selection.getSelectionHtml(this.document).replace(regexEmptyHTMLTags, ''), // Filter out empty blocks from selection
                  hasMultiParagraphs = selectionHTML.match(regexBlockElements); // Find how many block elements are within the html
  
              return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
          },
  
          modifySelection: function () {
              var selection = this.window.getSelection(),
                  selectionRange = selection.getRangeAt(0);
  
              /*
              * In firefox, there are cases (ie doubleclick of a word) where the selectionRange start
              * will be at the very end of an element.  In other browsers, the selectionRange start
              * would instead be at the very beginning of an element that actually has content.
              * example:
              *   <span>foo</span><span>bar</span>
              *
              * If the text 'bar' is selected, most browsers will have the selectionRange start at the beginning
              * of the 'bar' span.  However, there are cases where firefox will have the selectionRange start
              * at the end of the 'foo' span.  The contenteditable behavior will be ok, but if there are any
              * properties on the 'bar' span, they won't be reflected accurately in the toolbar
              * (ie 'Bold' button wouldn't be active)
              *
              * So, for cases where the selectionRange start is at the end of an element/node, find the next
              * adjacent text node that actually has content in it, and move the selectionRange start there.
              */
              if (this.standardizeSelectionStart &&
                      selectionRange.startContainer.nodeValue &&
                      (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) {
                  var adjacentNode = MediumEditor.util.findAdjacentTextNodeWithContent(MediumEditor.selection.getSelectionElement(this.window), selectionRange.startContainer, this.document);
                  if (adjacentNode) {
                      var offset = 0;
                      while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
                          offset = offset + 1;
                      }
                      selectionRange = MediumEditor.selection.select(this.document, adjacentNode, offset,
                          selectionRange.endContainer, selectionRange.endOffset);
                  }
              }
          },
  
          checkState: function () {
              if (this.base.preventSelectionUpdates) {
                  return;
              }
  
              // If no editable has focus OR selection is inside contenteditable = false
              // hide toolbar
              if (!this.base.getFocusedElement() ||
                      MediumEditor.selection.selectionInContentEditableFalse(this.window)) {
                  return this.hideToolbar();
              }
  
              // If there's no selection element, selection element doesn't belong to this editor
              // or toolbar is disabled for this selection element
              // hide toolbar
              var selectionElement = MediumEditor.selection.getSelectionElement(this.window);
              if (!selectionElement ||
                      this.getEditorElements().indexOf(selectionElement) === -1 ||
                      selectionElement.getAttribute('data-disable-toolbar')) {
                  return this.hideToolbar();
              }
  
              // Now we know there's a focused editable with a selection
  
              // If the updateOnEmptySelection option is true, show the toolbar
              if (this.updateOnEmptySelection && this.static) {
                  return this.showAndUpdateToolbar();
              }
  
              // If we don't have a 'valid' selection -> hide toolbar
              if (!MediumEditor.selection.selectionContainsContent(this.document) ||
                  (this.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) {
                  return this.hideToolbar();
              }
  
              this.showAndUpdateToolbar();
          },
  
          // Updating the toolbar
  
          showAndUpdateToolbar: function () {
              this.modifySelection();
              this.setToolbarButtonStates();
              this.trigger('positionToolbar', {}, this.base.getFocusedElement());
              this.showToolbarDefaultActions();
              this.setToolbarPosition();
          },
  
          setToolbarButtonStates: function () {
              this.forEachExtension(function (extension) {
                  if (typeof extension.isActive === 'function' &&
                      typeof extension.setInactive === 'function') {
                      extension.setInactive();
                  }
              });
  
              this.checkActiveButtons();
          },
  
          checkActiveButtons: function () {
              var manualStateChecks = [],
                  queryState = null,
                  selectionRange = MediumEditor.selection.getSelectionRange(this.document),
                  parentNode,
                  updateExtensionState = function (extension) {
                      if (typeof extension.checkState === 'function') {
                          extension.checkState(parentNode);
                      } else if (typeof extension.isActive === 'function' &&
                                 typeof extension.isAlreadyApplied === 'function' &&
                                 typeof extension.setActive === 'function') {
                          if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
                              extension.setActive();
                          }
                      }
                  };
  
              if (!selectionRange) {
                  return;
              }
  
              // Loop through all extensions
              this.forEachExtension(function (extension) {
                  // For those extensions where we can use document.queryCommandState(), do so
                  if (typeof extension.queryCommandState === 'function') {
                      queryState = extension.queryCommandState();
                      // If queryCommandState returns a valid value, we can trust the browser
                      // and don't need to do our manual checks
                      if (queryState !== null) {
                          if (queryState && typeof extension.setActive === 'function') {
                              extension.setActive();
                          }
                          return;
                      }
                  }
                  // We can't use queryCommandState for this extension, so add to manualStateChecks
                  manualStateChecks.push(extension);
              });
  
              parentNode = MediumEditor.selection.getSelectedParentElement(selectionRange);
  
              // Make sure the selection parent isn't outside of the contenteditable
              if (!this.getEditorElements().some(function (element) {
                      return MediumEditor.util.isDescendant(element, parentNode, true);
                  })) {
                  return;
              }
  
              // Climb up the DOM and do manual checks for whether a certain extension is currently enabled for this node
              while (parentNode) {
                  manualStateChecks.forEach(updateExtensionState);
  
                  // we can abort the search upwards if we leave the contentEditable element
                  if (MediumEditor.util.isMediumEditorElement(parentNode)) {
                      break;
                  }
                  parentNode = parentNode.parentNode;
              }
          },
  
          // Positioning toolbar
  
          positionToolbarIfShown: function () {
              if (this.isDisplayed()) {
                  this.setToolbarPosition();
              }
          },
  
          setToolbarPosition: function () {
              var container = this.base.getFocusedElement(),
                  selection = this.window.getSelection();
  
              // If there isn't a valid selection, bail
              if (!container) {
                  return this;
              }
  
              if (this.static || !selection.isCollapsed) {
                  this.showToolbar();
  
                  // we don't need any absolute positioning if relativeContainer is set
                  if (!this.relativeContainer) {
                      if (this.static) {
                          this.positionStaticToolbar(container);
                      } else {
                          this.positionToolbar(selection);
                      }
                  }
  
                  this.trigger('positionedToolbar', {}, this.base.getFocusedElement());
              }
          },
  
          positionStaticToolbar: function (container) {
              // position the toolbar at left 0, so we can get the real width of the toolbar
              this.getToolbarElement().style.left = '0';
  
              // document.documentElement for IE 9
              var scrollTop = (this.document.documentElement && this.document.documentElement.scrollTop) || this.document.body.scrollTop,
                  windowWidth = this.window.innerWidth,
                  toolbarElement = this.getToolbarElement(),
                  containerRect = container.getBoundingClientRect(),
                  containerTop = containerRect.top + scrollTop,
                  containerCenter = (containerRect.left + (containerRect.width / 2)),
                  toolbarHeight = toolbarElement.offsetHeight,
                  toolbarWidth = toolbarElement.offsetWidth,
                  halfOffsetWidth = toolbarWidth / 2,
                  targetLeft;
  
              if (this.sticky) {
                  // If it's beyond the height of the editor, position it at the bottom of the editor
                  if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) {
                      toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
                      toolbarElement.classList.remove('medium-editor-sticky-toolbar');
                  // Stick the toolbar to the top of the window
                  } else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) {
                      toolbarElement.classList.add('medium-editor-sticky-toolbar');
                      toolbarElement.style.top = this.stickyTopOffset + 'px';
                  // Normal static toolbar position
                  } else {
                      toolbarElement.classList.remove('medium-editor-sticky-toolbar');
                      toolbarElement.style.top = containerTop - toolbarHeight + 'px';
                  }
              } else {
                  toolbarElement.style.top = containerTop - toolbarHeight + 'px';
              }
  
              switch (this.align) {
                  case 'left':
                      targetLeft = containerRect.left;
                      break;
  
                  case 'right':
                      targetLeft = containerRect.right - toolbarWidth;
                      break;
  
                  case 'center':
                      targetLeft = containerCenter - halfOffsetWidth;
                      break;
              }
  
              if (targetLeft < 0) {
                  targetLeft = 0;
              } else if ((targetLeft + toolbarWidth) > windowWidth) {
                  targetLeft = (windowWidth - Math.ceil(toolbarWidth) - 1);
              }
  
              toolbarElement.style.left = targetLeft + 'px';
          },
  
          positionToolbar: function (selection) {
              // position the toolbar at left 0, so we can get the real width of the toolbar
              this.getToolbarElement().style.left = '0';
              this.getToolbarElement().style.right = 'initial';
  
              var range = selection.getRangeAt(0),
                  boundary = range.getBoundingClientRect();
  
              // Handle selections with just images
              if (!boundary || ((boundary.height === 0 && boundary.width === 0) && range.startContainer === range.endContainer)) {
                  // If there's a nested image, use that for the bounding rectangle
                  if (range.startContainer.nodeType === 1 && range.startContainer.querySelector('img')) {
                      boundary = range.startContainer.querySelector('img').getBoundingClientRect();
                  } else {
                      boundary = range.startContainer.getBoundingClientRect();
                  }
              }
  
              var containerWidth = this.window.innerWidth,
                  toolbarElement = this.getToolbarElement(),
                  toolbarHeight = toolbarElement.offsetHeight,
                  toolbarWidth = toolbarElement.offsetWidth,
                  halfOffsetWidth = toolbarWidth / 2,
                  buttonHeight = 50,
                  defaultLeft = this.diffLeft - halfOffsetWidth,
                  elementsContainer = this.getEditorOption('elementsContainer'),
                  elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
                  positions = {},
                  relativeBoundary = {},
                  middleBoundary, elementsContainerBoundary;
  
              // If container element is absolute / fixed, recalculate boundaries to be relative to the container
              if (elementsContainerAbsolute) {
                  elementsContainerBoundary = elementsContainer.getBoundingClientRect();
                  ['top', 'left'].forEach(function (key) {
                      relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key];
                  });
  
                  relativeBoundary.width = boundary.width;
                  relativeBoundary.height = boundary.height;
                  boundary = relativeBoundary;
  
                  containerWidth = elementsContainerBoundary.width;
  
                  // Adjust top position according to container scroll position
                  positions.top = elementsContainer.scrollTop;
              } else {
                  // Adjust top position according to window scroll position
                  positions.top = this.window.pageYOffset;
              }
  
              middleBoundary = boundary.left + boundary.width / 2;
              positions.top += boundary.top - toolbarHeight;
  
              if (boundary.top < buttonHeight) {
                  toolbarElement.classList.add('medium-toolbar-arrow-over');
                  toolbarElement.classList.remove('medium-toolbar-arrow-under');
                  positions.top += buttonHeight + boundary.height - this.diffTop;
              } else {
                  toolbarElement.classList.add('medium-toolbar-arrow-under');
                  toolbarElement.classList.remove('medium-toolbar-arrow-over');
                  positions.top += this.diffTop;
              }
  
              if (middleBoundary < halfOffsetWidth) {
                  positions.left = defaultLeft + halfOffsetWidth;
                  positions.right = 'initial';
              } else if ((containerWidth - middleBoundary) < halfOffsetWidth) {
                  positions.left = 'auto';
                  positions.right = 0;
              } else {
                  positions.left = defaultLeft + middleBoundary;
                  positions.right = 'initial';
              }
  
              ['top', 'left', 'right'].forEach(function (key) {
                  toolbarElement.style[key] = positions[key] + (isNaN(positions[key]) ? '' : 'px');
              });
          }
      });
  
      MediumEditor.extensions.toolbar = Toolbar;
  }());
  