nortmas
8/18/2015 - 2:46 PM

Plugin for Ckeditor

Plugin for Ckeditor

/**
 * @file plugin.js
 * Encadre plugin for Ckeditor.
 * Wraps the selected words/text in div element with custom class.
 */
(function() {

  CKEDITOR.plugins.add('encadre', {
    init: function(editor) {
      editor.ui.addButton('encadre', {
        label: 'Encadre',
        command: 'insertDiv',
        icon: this.path + 'icons/encadre.png'
      });
      editor.addCommand('insertDiv', {
        exec : function(editor) {
          divProceed(editor, 'encadre');
        }
      });
      editor.addCommand( 'removeDiv', {
        requiredContent: 'div',
        exec: function( editor ) {
          var selection = editor.getSelection(),
            ranges = selection && selection.getRanges(),
            range,
            bookmarks = selection.createBookmarks(),
            walker,
            toRemove = [];

          function findDiv( node ) {
            var div = CKEDITOR.plugins.div.getSurroundDiv( editor, node );
            if ( div && !div.data( 'cke-div-added' ) ) {
              toRemove.push( div );
              div.data( 'cke-div-added' );
            }
          }

          for ( var i = 0; i < ranges.length; i++ ) {
            range = ranges[ i ];
            if ( range.collapsed )
              findDiv( selection.getStartElement() );
            else {
              walker = new CKEDITOR.dom.walker( range );
              walker.evaluator = findDiv;
              walker.lastForward();
            }
          }

          for ( i = 0; i < toRemove.length; i++ )
            toRemove[ i ].remove( true );

          selection.selectBookmarks( bookmarks );
        }
      });

      if ( editor.addMenuItems && editor.contextMenu ) {
        editor.addMenuItems({
          removediv: {
            label: 'Remove Encadre block',
            command: 'removeDiv',
            group: 'div',
            order: 5
          }
        });
        editor.contextMenu.addListener( function( element ) {
          if ( !element || element.isReadOnly() )
            return null;

          if ( CKEDITOR.plugins.div.getSurroundDiv( editor ) ) {
            return {
              removediv: CKEDITOR.TRISTATE_OFF
            };
          }
          return null;
        });
      }
    }
  });

  CKEDITOR.plugins.div = {
    getSurroundDiv: function( editor, start ) {
      var path = editor.elementPath( start );
      return editor.elementPath( path.blockLimit ).contains( 'div', 1 );
    }
  };

  function divProceed(editor, className) {

    function createDiv(editor) {
      // new adding containers OR detected pre-existed containers.
      var containers = [];
      // node markers store.
      var database = {};
      // All block level elements which contained by the ranges.
      var containedBlocks = [],
        block;

      var divLimitDefinition = (function () {

        // Customzie from specialize blockLimit elements
        var definition = CKEDITOR.tools.extend({}, CKEDITOR.dtd.$blockLimit);

        if (editor.config.div_wrapTable) {
          delete definition.td;
          delete definition.th;
        }
        return definition;
      })();

      // DTD of 'div' element
      var dtd = CKEDITOR.dtd.div;

      // Get all ranges from the selection.
      var selection = editor.getSelection(),
        ranges = selection.getRanges();
      var bookmarks = selection.createBookmarks();
      var i, iterator;

      // Calcualte a default block tag if we need to create blocks.
      var blockTag = editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p';
      // collect all included elements from dom-iterator
      for (i = 0; i < ranges.length; i++) {
        iterator = ranges[i].createIterator();
        while (( block = iterator.getNextParagraph() )) {
          // include contents of blockLimit elements.
          if (block.getName() in divLimitDefinition) {
            var j,
              childNodes = block.getChildren();
            for (j = 0; j < childNodes.count(); j++)
              addSafely(containedBlocks, childNodes.getItem(j), database);
          } else {
            while (!dtd[block.getName()] && !block.equals(ranges[i].root))
              block = block.getParent();

            if (block.getName() == 'p' && block.getParent().getName() == 'figure') {
              var imgHtml = block.getHtml();
              var figureElement = block.getParent();
              figureElement.setHtml(imgHtml);
              block = figureElement.getParent();
            }

            addSafely(containedBlocks, block, database);
          }
        }
      }
      CKEDITOR.dom.element.clearAllMarkers(database);

      var blockGroups = groupByDivLimit(containedBlocks);
      var ancestor, blockEl, divElement;

      divElement = new CKEDITOR.dom.element('div', editor.document);
      divElement.setAttributes({class: className});

      //console.log(blockGroups);
      for (i = 0; i < blockGroups.length; i++) {
        var currentNode = blockGroups[i][0];

        // Calculate the common parent node of all contained elements.
        ancestor = currentNode.getParent();
        for (j = 1; j < blockGroups[i].length; j++)
          ancestor = ancestor.getCommonAncestor(blockGroups[i][j]);

        // Normalize the blocks in each group to a common parent.
        for (j = 0; j < blockGroups[i].length; j++) {
          currentNode = blockGroups[i][j];

          while (!currentNode.getParent().equals(ancestor))
            currentNode = currentNode.getParent();

          // This could introduce some duplicated elements in array.
          blockGroups[i][j] = currentNode;
        }

        // Wrapped blocks counting
        var fixedBlock = null;
        for (j = 0; j < blockGroups[i].length; j++) {
          currentNode = blockGroups[i][j];

          // Avoid DUP elements introduced by grouping.
          if (!( currentNode.getCustomData && currentNode.getCustomData('block_processed') )) {
            currentNode.is && CKEDITOR.dom.element.setMarker(database, currentNode, 'block_processed', true);

            // Establish new container, wrapping all elements in this group.
            if (!j)
              divElement.insertBefore(currentNode);

            divElement.append(currentNode);
          }
        }

        CKEDITOR.dom.element.clearAllMarkers(database);
        containers.push(divElement);
      }

      selection.selectBookmarks(bookmarks);
      return containers;
    }

    function groupByDivLimit(nodes) {
      var groups = [],
        lastDivLimit = null,
        path, block;
      for (var i = 0; i < nodes.length; i++) {
        block = nodes[i];
        var limit = getDivContainer(block);
        if (!limit.equals(lastDivLimit)) {
          lastDivLimit = limit;
          groups.push([]);
        }
        groups[groups.length - 1].push(block);
      }
      return groups;
    }

    function getDivContainer(element) {
      var container = editor.elementPath(element).blockLimit;

      // Dont stop at 'td' and 'th' when div should wrap entire table.
      if (editor.config.div_wrapTable && container.is(['td', 'th'])) {
        var parentPath = editor.elementPath(container.getParent());
        container = parentPath.blockLimit;
      }

      return container;
    }

    function addSafely(collection, element, database) {
      // 1. IE doesn't support customData on text nodes;
      // 2. Text nodes never get chance to appear twice;
      if (!element.is || !element.getCustomData('block_processed')) {
        element.is && CKEDITOR.dom.element.setMarker(database, element, 'block_processed', true);
        collection.push(element);
      }
    }

    return createDiv(editor);
  }

})();
<?php

/**
 * @file
 * M60 ckeditor.
 */

/**
 * Implements hook_ckeditor_plugin().
 */
function m60_ckeditor_ckeditor_plugin() {
  return array('encadre' => array(
    'name' => 'encadre',
    'desc' => t('Plugin adds simple html block with class encadre'),
    'path' => drupal_get_path('module', 'm60_ckeditor') . '/plugins/encadre/',
    'buttons' => array(
      'encadre' => array(
        'icon' => 'icons/encadre.png',
        'label' => 'Encadre',
      )
    )
  ));
}