text: ﻿/*
   Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
   For licensing, see LICENSE.html or http://ckeditor.com/license
   */
  
  /**
   * @file Increse and decrease indent commands.
   */
  
  (function () {
      var listNodeNames = { ol: 1, ul: 1 },
          isNotWhitespaces = CKEDITOR.dom.walker.whitespaces(true),
          isNotBookmark = CKEDITOR.dom.walker.bookmark(false, true);
  
      function onSelectionChange(evt) {
          if (evt.editor.readOnly)
              return null;
  
          var editor = evt.editor,
              elementPath = evt.data.path,
              list = elementPath && elementPath.contains(listNodeNames),
              firstBlock = elementPath.block || elementPath.blockLimit;
  
          if (list)
              return this.setState(CKEDITOR.TRISTATE_OFF);
  
          if (!this.useIndentClasses && this.name == 'indent')
              return this.setState(CKEDITOR.TRISTATE_OFF);
  
          if (!firstBlock)
              return this.setState(CKEDITOR.TRISTATE_DISABLED);
  
          if (this.useIndentClasses) {
              var indentClass = firstBlock.$.className.match(this.classNameRegex),
                  indentStep = 0;
              if (indentClass) {
                  indentClass = indentClass[1];
                  indentStep = this.indentClassMap[ indentClass ];
              }
              if (( this.name == 'outdent' && !indentStep ) ||
                  ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ))
                  return this.setState(CKEDITOR.TRISTATE_DISABLED);
              return this.setState(CKEDITOR.TRISTATE_OFF);
          }
          else {
              var indent = parseInt(firstBlock.getStyle(getIndentCssProperty(firstBlock)), 10);
              if (isNaN(indent))
                  indent = 0;
              if (indent <= 0)
                  return this.setState(CKEDITOR.TRISTATE_DISABLED);
              return this.setState(CKEDITOR.TRISTATE_OFF);
          }
      }
  
      function indentCommand(editor, name) {
          this.name = name;
          this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0;
          if (this.useIndentClasses) {
              this.classNameRegex = new RegExp('(?:^|\\s+)(' + editor.config.indentClasses.join('|') + ')(?=$|\\s)');
              this.indentClassMap = {};
              for (var i = 0; i < editor.config.indentClasses.length; i++)
                  this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1;
          }
  
          this.startDisabled = name == 'outdent';
      }
  
      // Returns the CSS property to be used for identing a given element.
      function getIndentCssProperty(element, dir) {
          return ( dir || element.getComputedStyle('direction') ) == 'ltr' ? 'margin-left' : 'margin-right';
      }
  
      function isListItem(node) {
          return node.type == CKEDITOR.NODE_ELEMENT && node.is('li');
      }
  
      indentCommand.prototype = {
          exec: function (editor) {
              var self = this, database = {};
  
              function indentList(listNode) {
                  // Our starting and ending points of the range might be inside some blocks under a list item...
                  // So before playing with the iterator, we need to expand the block to include the list items.
                  var startContainer = range.startContainer,
                      endContainer = range.endContainer;
                  while (startContainer && !startContainer.getParent().equals(listNode))
                      startContainer = startContainer.getParent();
                  while (endContainer && !endContainer.getParent().equals(listNode))
                      endContainer = endContainer.getParent();
  
                  if (!startContainer || !endContainer)
                      return;
  
                  // Now we can iterate over the individual items on the same tree depth.
                  var block = startContainer,
                      itemsToMove = [],
                      stopFlag = false;
                  while (!stopFlag) {
                      if (block.equals(endContainer))
                          stopFlag = true;
                      itemsToMove.push(block);
                      block = block.getNext();
                  }
                  if (itemsToMove.length < 1)
                      return;
  
                  // Do indent or outdent operations on the array model of the list, not the
                  // list's DOM tree itself. The array model demands that it knows as much as
                  // possible about the surrounding lists, we need to feed it the further
                  // ancestor node that is still a list.
                  var listParents = listNode.getParents(true);
                  for (var i = 0; i < listParents.length; i++) {
                      if (listParents[i].getName && listNodeNames[ listParents[i].getName() ]) {
                          listNode = listParents[i];
                          break;
                      }
                  }
                  var indentOffset = self.name == 'indent' ? 1 : -1,
                      startItem = itemsToMove[0],
                      lastItem = itemsToMove[ itemsToMove.length - 1 ];
  
                  // Convert the list DOM tree into a one dimensional array.
                  var listArray = CKEDITOR.plugins.list.listToArray(listNode, database);
  
                  // Apply indenting or outdenting on the array.
                  var baseIndent = listArray[ lastItem.getCustomData('listarray_index') ].indent;
                  for (i = startItem.getCustomData('listarray_index'); i <= lastItem.getCustomData('listarray_index'); i++) {
                      listArray[ i ].indent += indentOffset;
                      // Make sure the newly created sublist get a brand-new element of the same type. (#5372)
                      var listRoot = listArray[ i ].parent;
                      listArray[ i ].parent = new CKEDITOR.dom.element(listRoot.getName(), listRoot.getDocument());
                  }
  
                  for (i = lastItem.getCustomData('listarray_index') + 1;
                       i < listArray.length && listArray[i].indent > baseIndent; i++)
                      listArray[i].indent += indentOffset;
  
                  // Convert the array back to a DOM forest (yes we might have a few subtrees now).
                  // And replace the old list with the new forest.
                  var newList = CKEDITOR.plugins.list.arrayToList(listArray, database, null, editor.config.enterMode, listNode.getDirection());
  
                  // Avoid nested <li> after outdent even they're visually same,
                  // recording them for later refactoring.(#3982)
                  if (self.name == 'outdent') {
                      var parentLiElement;
                      if (( parentLiElement = listNode.getParent() ) && parentLiElement.is('li')) {
                          var children = newList.listNode.getChildren(),
                              pendingLis = [],
                              count = children.count(),
                              child;
  
                          for (i = count - 1; i >= 0; i--) {
                              if (( child = children.getItem(i) ) && child.is && child.is('li'))
                                  pendingLis.push(child);
                          }
                      }
                  }
  
                  if (newList)
                      newList.listNode.replace(listNode);
  
                  // Move the nested <li> to be appeared after the parent.
                  if (pendingLis && pendingLis.length) {
                      for (i = 0; i < pendingLis.length; i++) {
                          var li = pendingLis[ i ],
                              followingList = li;
  
                          // Nest preceding <ul>/<ol> inside current <li> if any.
                          while (( followingList = followingList.getNext() ) &&
                              followingList.is &&
                              followingList.getName() in listNodeNames) {
                              // IE requires a filler NBSP for nested list inside empty list item,
                              // otherwise the list item will be inaccessiable. (#4476)
                              if (CKEDITOR.env.ie && !li.getFirst(function (node) {
                                  return isNotWhitespaces(node) && isNotBookmark(node);
                              }))
                                  li.append(range.document.createText('\u00a0'));
  
                              li.append(followingList);
                          }
  
                          li.insertAfter(parentLiElement);
                      }
                  }
              }
  
              function indentBlock() {
                  var iterator = range.createIterator(),
                      enterMode = editor.config.enterMode;
                  iterator.enforceRealBlocks = true;
                  iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
                  var block;
                  while (( block = iterator.getNextParagraph(enterMode == CKEDITOR.ENTER_P ? 'p' : 'div') ))
                      indentElement(block);
              }
  
              function indentElement(element, dir) {
                  if (element.getCustomData('indent_processed'))
                      return false;
  
                  if (self.useIndentClasses) {
                      // Transform current class name to indent step index.
                      var indentClass = element.$.className.match(self.classNameRegex),
                          indentStep = 0;
                      if (indentClass) {
                          indentClass = indentClass[1];
                          indentStep = self.indentClassMap[ indentClass ];
                      }
  
                      // Operate on indent step index, transform indent step index back to class
                      // name.
                      if (self.name == 'outdent')
                          indentStep--;
                      else
                          indentStep++;
  
                      if (indentStep < 0)
                          return false;
  
                      indentStep = Math.min(indentStep, editor.config.indentClasses.length);
                      indentStep = Math.max(indentStep, 0);
                      element.$.className = CKEDITOR.tools.ltrim(element.$.className.replace(self.classNameRegex, ''));
                      if (indentStep > 0)
                          element.addClass(editor.config.indentClasses[ indentStep - 1 ]);
                  }
                  else {
                      var indentCssProperty = getIndentCssProperty(element, dir),
                          currentOffset = parseInt(element.getStyle(indentCssProperty), 10);
                      if (isNaN(currentOffset))
                          currentOffset = 0;
                      var indentOffset = editor.config.indentOffset || 40;
                      currentOffset += ( self.name == 'indent' ? 1 : -1 ) * indentOffset;
  
                      if (currentOffset < 0)
                          return false;
  
                      currentOffset = Math.max(currentOffset, 0);
                      currentOffset = Math.ceil(currentOffset / indentOffset) * indentOffset;
                      element.setStyle(indentCssProperty, currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : '');
                      if (element.getAttribute('style') === '')
                          element.removeAttribute('style');
                  }
  
                  CKEDITOR.dom.element.setMarker(database, element, 'indent_processed', 1);
                  return true;
              }
  
              var selection = editor.getSelection(),
                  bookmarks = selection.createBookmarks(1),
                  ranges = selection && selection.getRanges(1),
                  range;
  
  
              var iterator = ranges.createIterator();
              while (( range = iterator.getNextRange() )) {
                  var rangeRoot = range.getCommonAncestor(),
                      nearestListBlock = rangeRoot;
  
                  while (nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT &&
                      listNodeNames[ nearestListBlock.getName() ] ))
                      nearestListBlock = nearestListBlock.getParent();
  
                  // Avoid having selection enclose the entire list. (#6138)
                  // [<ul><li>...</li></ul>] =><ul><li>[...]</li></ul>
                  if (!nearestListBlock) {
                      var selectedNode = range.getEnclosedNode();
                      if (selectedNode
                          && selectedNode.type == CKEDITOR.NODE_ELEMENT
                          && selectedNode.getName() in listNodeNames) {
                          range.setStartAt(selectedNode, CKEDITOR.POSITION_AFTER_START);
                          range.setEndAt(selectedNode, CKEDITOR.POSITION_BEFORE_END);
                          nearestListBlock = selectedNode;
                      }
                  }
  
                  // Avoid selection anchors under list root.
                  // <ul>[<li>...</li>]</ul> =>	<ul><li>[...]</li></ul>
                  if (nearestListBlock && range.startContainer.type == CKEDITOR.NODE_ELEMENT
                      && range.startContainer.getName() in listNodeNames) {
                      var walker = new CKEDITOR.dom.walker(range);
                      walker.evaluator = isListItem;
                      range.startContainer = walker.next();
                  }
  
                  if (nearestListBlock && range.endContainer.type == CKEDITOR.NODE_ELEMENT
                      && range.endContainer.getName() in listNodeNames) {
                      walker = new CKEDITOR.dom.walker(range);
                      walker.evaluator = isListItem;
                      range.endContainer = walker.previous();
                  }
  
                  if (nearestListBlock) {
                      var firstListItem = nearestListBlock.getFirst(isListItem),
                          hasMultipleItems = !!firstListItem.getNext(isListItem),
                          rangeStart = range.startContainer,
                          indentWholeList = firstListItem.equals(rangeStart) || firstListItem.contains(rangeStart);
  
                      // Indent the entire list if cursor is inside the first list item. (#3893)
                      // Only do that for indenting or when using indent classes or when there is something to outdent. (#6141)
                      if (!( indentWholeList &&
                          ( self.name == 'indent' || self.useIndentClasses || parseInt(nearestListBlock.getStyle(getIndentCssProperty(nearestListBlock)), 10) ) &&
                          indentElement(nearestListBlock, !hasMultipleItems && firstListItem.getDirection()) ))
                          indentList(nearestListBlock);
                  }
                  else
                      indentBlock();
              }
  
              // Clean up the markers.
              CKEDITOR.dom.element.clearAllMarkers(database);
  
              editor.forceNextSelectionCheck();
              selection.selectBookmarks(bookmarks);
          }
      };
  
      CKEDITOR.plugins.add('indent',
          {
              init: function (editor) {
                  // Register commands.
                  var indent = editor.addCommand('indent', new indentCommand(editor, 'indent')),
                      outdent = editor.addCommand('outdent', new indentCommand(editor, 'outdent'));
  
                  // Register the toolbar buttons.
                  editor.ui.addButton('Indent',
                      {
                          label: editor.lang.indent,
                          command: 'indent'
                      });
                  editor.ui.addButton('Outdent',
                      {
                          label: editor.lang.outdent,
                          command: 'outdent'
                      });
  
                  // Register the state changing handlers.
                  editor.on('selectionChange', CKEDITOR.tools.bind(onSelectionChange, indent));
                  editor.on('selectionChange', CKEDITOR.tools.bind(onSelectionChange, outdent));
  
                  // [IE6/7] Raw lists are using margin instead of padding for visual indentation in wysiwyg mode. (#3893)
                  if (CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat) {
                      editor.addCss(
                          "ul,ol" +
                              "{" +
                              "	margin-left: 0px;" +
                              "	padding-left: 40px;" +
                              "}");
                  }
  
                  // Register dirChanged listener.
                  editor.on('dirChanged', function (e) {
                      var range = new CKEDITOR.dom.range(editor.document);
                      range.setStartBefore(e.data.node);
                      range.setEndAfter(e.data.node);
  
                      var walker = new CKEDITOR.dom.walker(range),
                          node;
  
                      while (( node = walker.next() )) {
                          if (node.type == CKEDITOR.NODE_ELEMENT) {
                              // A child with the defined dir is to be ignored.
                              if (!node.equals(e.data.node) && node.getDirection()) {
                                  range.setStartAfter(node);
                                  walker = new CKEDITOR.dom.walker(range);
                                  continue;
                              }
  
                              // Switch alignment classes.
                              var classes = editor.config.indentClasses;
                              if (classes) {
                                  var suffix = ( e.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];
                                  for (var i = 0; i < classes.length; i++) {
                                      if (node.hasClass(classes[ i ] + suffix[ 0 ])) {
                                          node.removeClass(classes[ i ] + suffix[ 0 ]);
                                          node.addClass(classes[ i ] + suffix[ 1 ]);
                                      }
                                  }
                              }
  
                              // Switch the margins.
                              var marginLeft = node.getStyle('margin-right'),
                                  marginRight = node.getStyle('margin-left');
  
                              marginLeft ? node.setStyle('margin-left', marginLeft) : node.removeStyle('margin-left');
                              marginRight ? node.setStyle('margin-right', marginRight) : node.removeStyle('margin-right');
                          }
                      }
                  });
              },
  
              requires: [ 'domiterator', 'list' ]
          });
  })();
  
  /**
   * Size of each indentation step
   * @name CKEDITOR.config.indentOffset
   * @type Number
   * @default 40
   * @example
   * config.indentOffset = 4;
   */
  
  /**
   * Unit for the indentation style
   * @name CKEDITOR.config.indentUnit
   * @type String
   * @default 'px'
   * @example
   * config.indentUnit = 'em';
   */
  
  /**
   * List of classes to use for indenting the contents. If it's null, no classes will be used
   * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used.
   * @name CKEDITOR.config.indentClasses
   * @type Array
   * @default null
   * @example
   * // Use the classes 'Indent1', 'Indent2', 'Indent3'
   * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];
   */
  