[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/editor/atto/yui/src/editor/js/ -> editor.js (source)

   1  // This file is part of Moodle - http://moodle.org/
   2  //
   3  // Moodle is free software: you can redistribute it and/or modify
   4  // it under the terms of the GNU General Public License as published by
   5  // the Free Software Foundation, either version 3 of the License, or
   6  // (at your option) any later version.
   7  //
   8  // Moodle is distributed in the hope that it will be useful,
   9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11  // GNU General Public License for more details.
  12  //
  13  // You should have received a copy of the GNU General Public License
  14  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  15  /* eslint-disable no-unused-vars */
  16  
  17  /**
  18   * The Atto WYSIWG pluggable editor, written for Moodle.
  19   *
  20   * @module     moodle-editor_atto-editor
  21   * @package    editor_atto
  22   * @copyright  2013 Damyon Wiese  <damyon@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @main       moodle-editor_atto-editor
  25   */
  26  
  27  /**
  28   * @module moodle-editor_atto-editor
  29   * @submodule editor-base
  30   */
  31  
  32  var LOGNAME = 'moodle-editor_atto-editor';
  33  var CSS = {
  34          CONTENT: 'editor_atto_content',
  35          CONTENTWRAPPER: 'editor_atto_content_wrap',
  36          TOOLBAR: 'editor_atto_toolbar',
  37          WRAPPER: 'editor_atto',
  38          HIGHLIGHT: 'highlight'
  39      },
  40      rangy = window.rangy;
  41  
  42  /**
  43   * The Atto editor for Moodle.
  44   *
  45   * @namespace M.editor_atto
  46   * @class Editor
  47   * @constructor
  48   * @uses M.editor_atto.EditorClean
  49   * @uses M.editor_atto.EditorFilepicker
  50   * @uses M.editor_atto.EditorSelection
  51   * @uses M.editor_atto.EditorStyling
  52   * @uses M.editor_atto.EditorTextArea
  53   * @uses M.editor_atto.EditorToolbar
  54   * @uses M.editor_atto.EditorToolbarNav
  55   */
  56  
  57  function Editor() {
  58      Editor.superclass.constructor.apply(this, arguments);
  59  }
  60  
  61  Y.extend(Editor, Y.Base, {
  62  
  63      /**
  64       * List of known block level tags.
  65       * Taken from "https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements".
  66       *
  67       * @property BLOCK_TAGS
  68       * @type {Array}
  69       */
  70      BLOCK_TAGS: [
  71          'address',
  72          'article',
  73          'aside',
  74          'audio',
  75          'blockquote',
  76          'canvas',
  77          'dd',
  78          'div',
  79          'dl',
  80          'fieldset',
  81          'figcaption',
  82          'figure',
  83          'footer',
  84          'form',
  85          'h1',
  86          'h2',
  87          'h3',
  88          'h4',
  89          'h5',
  90          'h6',
  91          'header',
  92          'hgroup',
  93          'hr',
  94          'noscript',
  95          'ol',
  96          'output',
  97          'p',
  98          'pre',
  99          'section',
 100          'table',
 101          'tfoot',
 102          'ul',
 103          'video'
 104      ],
 105  
 106      PLACEHOLDER_CLASS: 'atto-tmp-class',
 107      ALL_NODES_SELECTOR: '[style],font[face]',
 108      FONT_FAMILY: 'fontFamily',
 109  
 110      /**
 111       * The wrapper containing the editor.
 112       *
 113       * @property _wrapper
 114       * @type Node
 115       * @private
 116       */
 117      _wrapper: null,
 118  
 119      /**
 120       * A reference to the content editable Node.
 121       *
 122       * @property editor
 123       * @type Node
 124       */
 125      editor: null,
 126  
 127      /**
 128       * A reference to the original text area.
 129       *
 130       * @property textarea
 131       * @type Node
 132       */
 133      textarea: null,
 134  
 135      /**
 136       * A reference to the label associated with the original text area.
 137       *
 138       * @property textareaLabel
 139       * @type Node
 140       */
 141      textareaLabel: null,
 142  
 143      /**
 144       * A reference to the list of plugins.
 145       *
 146       * @property plugins
 147       * @type object
 148       */
 149      plugins: null,
 150  
 151      /**
 152       * Event Handles to clear on editor destruction.
 153       *
 154       * @property _eventHandles
 155       * @private
 156       */
 157      _eventHandles: null,
 158  
 159      initializer: function() {
 160          var template;
 161  
 162          // Note - it is not safe to use a CSS selector like '#' + elementid because the id
 163          // may have colons in it - e.g.  quiz.
 164          this.textarea = Y.one(document.getElementById(this.get('elementid')));
 165  
 166          if (!this.textarea) {
 167              // No text area found.
 168              Y.log('Text area not found - unable to setup editor for ' + this.get('elementid'),
 169                      'error', LOGNAME);
 170              return;
 171          }
 172  
 173          this._eventHandles = [];
 174  
 175          this._wrapper = Y.Node.create('<div class="' + CSS.WRAPPER + '" />');
 176          template = Y.Handlebars.compile('<div id="{{elementid}}editable" ' +
 177                  'contenteditable="true" ' +
 178                  'role="textbox" ' +
 179                  'spellcheck="true" ' +
 180                  'aria-live="off" ' +
 181                  'class="{{CSS.CONTENT}}" ' +
 182                  '/>');
 183          this.editor = Y.Node.create(template({
 184              elementid: this.get('elementid'),
 185              CSS: CSS
 186          }));
 187  
 188          // Add a labelled-by attribute to the contenteditable.
 189          this.textareaLabel = Y.one('[for="' + this.get('elementid') + '"]');
 190          if (this.textareaLabel) {
 191              this.textareaLabel.generateID();
 192              this.editor.setAttribute('aria-labelledby', this.textareaLabel.get("id"));
 193          }
 194  
 195          // Add everything to the wrapper.
 196          this.setupToolbar();
 197  
 198          // Editable content wrapper.
 199          var content = Y.Node.create('<div class="' + CSS.CONTENTWRAPPER + '" />');
 200          content.appendChild(this.editor);
 201          this._wrapper.appendChild(content);
 202  
 203          // Style the editor. According to the styles.css: 20 is the line-height, 8 is padding-top + padding-bottom.
 204          this.editor.setStyle('minHeight', ((20 * this.textarea.getAttribute('rows')) + 8) + 'px');
 205  
 206          if (Y.UA.ie === 0) {
 207              // We set a height here to force the overflow because decent browsers allow the CSS property resize.
 208              this.editor.setStyle('height', ((20 * this.textarea.getAttribute('rows')) + 8) + 'px');
 209          }
 210  
 211          // Disable odd inline CSS styles.
 212          this.disableCssStyling();
 213  
 214          // Use paragraphs not divs.
 215          if (document.queryCommandSupported('DefaultParagraphSeparator')) {
 216              document.execCommand('DefaultParagraphSeparator', false, 'p');
 217          }
 218  
 219          // Add the toolbar and editable zone to the page.
 220          this.textarea.get('parentNode').insert(this._wrapper, this.textarea).
 221                  setAttribute('class', 'editor_atto_wrap');
 222  
 223          // Hide the old textarea.
 224          this.textarea.hide();
 225  
 226          // Copy the text to the contenteditable div.
 227          this.updateFromTextArea();
 228  
 229          // Publish the events that are defined by this editor.
 230          this.publishEvents();
 231  
 232          // Add handling for saving and restoring selections on cursor/focus changes.
 233          this.setupSelectionWatchers();
 234  
 235          // Add polling to update the textarea periodically when typing long content.
 236          this.setupAutomaticPolling();
 237  
 238          // Setup plugins.
 239          this.setupPlugins();
 240  
 241          // Initialize the auto-save timer.
 242          this.setupAutosave();
 243          // Preload the icons for the notifications.
 244          this.setupNotifications();
 245      },
 246  
 247      /**
 248       * Focus on the editable area for this editor.
 249       *
 250       * @method focus
 251       * @chainable
 252       */
 253      focus: function() {
 254          this.editor.focus();
 255  
 256          return this;
 257      },
 258  
 259      /**
 260       * Publish events for this editor instance.
 261       *
 262       * @method publishEvents
 263       * @private
 264       * @chainable
 265       */
 266      publishEvents: function() {
 267          /**
 268           * Fired when changes are made within the editor.
 269           *
 270           * @event change
 271           */
 272          this.publish('change', {
 273              broadcast: true,
 274              preventable: true
 275          });
 276  
 277          /**
 278           * Fired when all plugins have completed loading.
 279           *
 280           * @event pluginsloaded
 281           */
 282          this.publish('pluginsloaded', {
 283              fireOnce: true
 284          });
 285  
 286          this.publish('atto:selectionchanged', {
 287              prefix: 'atto'
 288          });
 289  
 290          return this;
 291      },
 292  
 293      /**
 294       * Set up automated polling of the text area to update the textarea.
 295       *
 296       * @method setupAutomaticPolling
 297       * @chainable
 298       */
 299      setupAutomaticPolling: function() {
 300          this._registerEventHandle(this.editor.on(['keyup', 'cut'], this.updateOriginal, this));
 301          this._registerEventHandle(this.editor.on('paste', this.pasteCleanup, this));
 302  
 303          // Call this.updateOriginal after dropped content has been processed.
 304          this._registerEventHandle(this.editor.on('drop', this.updateOriginalDelayed, this));
 305  
 306          return this;
 307      },
 308  
 309      /**
 310       * Calls updateOriginal on a short timer to allow native event handlers to run first.
 311       *
 312       * @method updateOriginalDelayed
 313       * @chainable
 314       */
 315      updateOriginalDelayed: function() {
 316          Y.soon(Y.bind(this.updateOriginal, this));
 317  
 318          return this;
 319      },
 320  
 321      setupPlugins: function() {
 322          // Clear the list of plugins.
 323          this.plugins = {};
 324  
 325          var plugins = this.get('plugins');
 326  
 327          var groupIndex,
 328              group,
 329              pluginIndex,
 330              plugin,
 331              pluginConfig;
 332  
 333          for (groupIndex in plugins) {
 334              group = plugins[groupIndex];
 335              if (!group.plugins) {
 336                  // No plugins in this group - skip it.
 337                  continue;
 338              }
 339              for (pluginIndex in group.plugins) {
 340                  plugin = group.plugins[pluginIndex];
 341  
 342                  pluginConfig = Y.mix({
 343                      name: plugin.name,
 344                      group: group.group,
 345                      editor: this.editor,
 346                      toolbar: this.toolbar,
 347                      host: this
 348                  }, plugin);
 349  
 350                  // Add a reference to the current editor.
 351                  if (typeof Y.M['atto_' + plugin.name] === "undefined") {
 352                      Y.log("Plugin '" + plugin.name + "' could not be found - skipping initialisation", "warn", LOGNAME);
 353                      continue;
 354                  }
 355                  this.plugins[plugin.name] = new Y.M['atto_' + plugin.name].Button(pluginConfig);
 356              }
 357          }
 358  
 359          // Some plugins need to perform actions once all plugins have loaded.
 360          this.fire('pluginsloaded');
 361  
 362          return this;
 363      },
 364  
 365      enablePlugins: function(plugin) {
 366          this._setPluginState(true, plugin);
 367      },
 368  
 369      disablePlugins: function(plugin) {
 370          this._setPluginState(false, plugin);
 371      },
 372  
 373      _setPluginState: function(enable, plugin) {
 374          var target = 'disableButtons';
 375          if (enable) {
 376              target = 'enableButtons';
 377          }
 378  
 379          if (plugin) {
 380              this.plugins[plugin][target]();
 381          } else {
 382              Y.Object.each(this.plugins, function(currentPlugin) {
 383                  currentPlugin[target]();
 384              }, this);
 385          }
 386      },
 387  
 388      /**
 389       * Register an event handle for disposal in the destructor.
 390       *
 391       * @method _registerEventHandle
 392       * @param {EventHandle} The Event Handle as returned by Y.on, and Y.delegate.
 393       * @private
 394       */
 395      _registerEventHandle: function(handle) {
 396          this._eventHandles.push(handle);
 397      }
 398  
 399  }, {
 400      NS: 'editor_atto',
 401      ATTRS: {
 402          /**
 403           * The unique identifier for the form element representing the editor.
 404           *
 405           * @attribute elementid
 406           * @type String
 407           * @writeOnce
 408           */
 409          elementid: {
 410              value: null,
 411              writeOnce: true
 412          },
 413  
 414          /**
 415           * The contextid of the form.
 416           *
 417           * @attribute contextid
 418           * @type Integer
 419           * @writeOnce
 420           */
 421          contextid: {
 422              value: null,
 423              writeOnce: true
 424          },
 425  
 426          /**
 427           * Plugins with their configuration.
 428           *
 429           * The plugins structure is:
 430           *
 431           *     [
 432           *         {
 433           *             "group": "groupName",
 434           *             "plugins": [
 435           *                 "pluginName": {
 436           *                     "configKey": "configValue"
 437           *                 },
 438           *                 "pluginName": {
 439           *                     "configKey": "configValue"
 440           *                 }
 441           *             ]
 442           *         },
 443           *         {
 444           *             "group": "groupName",
 445           *             "plugins": [
 446           *                 "pluginName": {
 447           *                     "configKey": "configValue"
 448           *                 }
 449           *             ]
 450           *         }
 451           *     ]
 452           *
 453           * @attribute plugins
 454           * @type Object
 455           * @writeOnce
 456           */
 457          plugins: {
 458              value: {},
 459              writeOnce: true
 460          }
 461      }
 462  });
 463  
 464  // The Editor publishes custom events that can be subscribed to.
 465  Y.augment(Editor, Y.EventTarget);
 466  
 467  Y.namespace('M.editor_atto').Editor = Editor;
 468  
 469  // Function for Moodle's initialisation.
 470  Y.namespace('M.editor_atto.Editor').init = function(config) {
 471      return new Y.M.editor_atto.Editor(config);
 472  };


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1