[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/ -> moodle-atto_link-button.js (source)

   1  YUI.add('moodle-atto_link-button', function (Y, NAME) {
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /*
  19   * @package    atto_link
  20   * @copyright  2013 Damyon Wiese  <damyon@moodle.com>
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  /**
  25   * @module moodle-atto_link-button
  26   */
  27  
  28  /**
  29   * Atto text editor link plugin.
  30   *
  31   * @namespace M.atto_link
  32   * @class button
  33   * @extends M.editor_atto.EditorPlugin
  34   */
  35  
  36  var COMPONENTNAME = 'atto_link',
  37      CSS = {
  38          NEWWINDOW: 'atto_link_openinnewwindow',
  39          URLINPUT: 'atto_link_urlentry'
  40      },
  41      SELECTORS = {
  42          URLINPUT: '.atto_link_urlentry'
  43      },
  44      TEMPLATE = '' +
  45              '<form class="atto_form">' +
  46                  '<label for="{{elementid}}_atto_link_urlentry">{{get_string "enterurl" component}}</label>' +
  47                  '<input class="fullwidth url {{CSS.URLINPUT}}" type="url" id="{{elementid}}_atto_link_urlentry" size="32"/><br/>' +
  48  
  49                  // Add the repository browser button.
  50                  '{{#if showFilepicker}}' +
  51                      '<button class="openlinkbrowser">{{get_string "browserepositories" component}}</button>' +
  52                      '<br/>' +
  53                  '{{/if}}' +
  54                  '<input type="checkbox" class="newwindow" id="{{elementid}}_{{CSS.NEWWINDOW}}"/>' +
  55                  '<label class="sameline" for="{{elementid}}_{{CSS.NEWWINDOW}}">{{get_string "openinnewwindow" component}}</label>' +
  56                  '<br/>' +
  57                  '<div class="mdl-align">' +
  58                      '<br/>' +
  59                      '<button type="submit" class="submit">{{get_string "createlink" component}}</button>' +
  60                  '</div>' +
  61              '</form>';
  62  Y.namespace('M.atto_link').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
  63  
  64      /**
  65       * A reference to the current selection at the time that the dialogue
  66       * was opened.
  67       *
  68       * @property _currentSelection
  69       * @type Range
  70       * @private
  71       */
  72      _currentSelection: null,
  73  
  74      /**
  75       * A reference to the dialogue content.
  76       *
  77       * @property _content
  78       * @type Node
  79       * @private
  80       */
  81      _content: null,
  82  
  83      initializer: function() {
  84          // Add the link button first.
  85          this.addButton({
  86              icon: 'e/insert_edit_link',
  87              keys: '75',
  88              callback: this._displayDialogue,
  89              tags: 'a',
  90              tagMatchRequiresAll: false
  91          });
  92  
  93          // And then the unlink button.
  94          this.addButton({
  95              buttonName: 'unlink',
  96              callback: this._unlink,
  97              icon: 'e/remove_link',
  98              title: 'unlink',
  99  
 100              // Watch the following tags and add/remove highlighting as appropriate:
 101              tags: 'a',
 102              tagMatchRequiresAll: false
 103          });
 104      },
 105  
 106      /**
 107       * Display the link editor.
 108       *
 109       * @method _displayDialogue
 110       * @private
 111       */
 112      _displayDialogue: function() {
 113          // Store the current selection.
 114          this._currentSelection = this.get('host').getSelection();
 115          if (this._currentSelection === false) {
 116              return;
 117          }
 118  
 119          var dialogue = this.getDialogue({
 120              headerContent: M.util.get_string('createlink', COMPONENTNAME),
 121              focusAfterHide: true,
 122              focusOnShowSelector: SELECTORS.URLINPUT
 123          });
 124  
 125          // Set the dialogue content, and then show the dialogue.
 126          dialogue.set('bodyContent', this._getDialogueContent());
 127  
 128          // Resolve anchors in the selected text.
 129          this._resolveAnchors();
 130          dialogue.show();
 131      },
 132  
 133      /**
 134       * If there is selected text and it is part of an anchor link,
 135       * extract the url (and target) from the link (and set them in the form).
 136       *
 137       * @method _resolveAnchors
 138       * @private
 139       */
 140      _resolveAnchors: function() {
 141          // Find the first anchor tag in the selection.
 142          var selectednode = this.get('host').getSelectionParentNode(),
 143              anchornodes,
 144              anchornode,
 145              url,
 146              target;
 147  
 148          // Note this is a document fragment and YUI doesn't like them.
 149          if (!selectednode) {
 150              return;
 151          }
 152  
 153          anchornodes = this._findSelectedAnchors(Y.one(selectednode));
 154          if (anchornodes.length > 0) {
 155              anchornode = anchornodes[0];
 156              this._currentSelection = this.get('host').getSelectionFromNode(anchornode);
 157              url = anchornode.getAttribute('href');
 158              target = anchornode.getAttribute('target');
 159              if (url !== '') {
 160                  this._content.one('.url').setAttribute('value', url);
 161              }
 162              if (target === '_blank') {
 163                  this._content.one('.newwindow').setAttribute('checked', 'checked');
 164              } else {
 165                  this._content.one('.newwindow').removeAttribute('checked');
 166              }
 167          }
 168      },
 169  
 170      /**
 171       * Update the dialogue after a link was selected in the File Picker.
 172       *
 173       * @method _filepickerCallback
 174       * @param {object} params The parameters provided by the filepicker
 175       * containing information about the link.
 176       * @private
 177       */
 178      _filepickerCallback: function(params) {
 179          this.getDialogue()
 180                  .set('focusAfterHide', null)
 181                  .hide();
 182  
 183          if (params.url !== '') {
 184              // Add the link.
 185              this._setLinkOnSelection(params.url);
 186  
 187              // And mark the text area as updated.
 188              this.markUpdated();
 189          }
 190      },
 191  
 192      /**
 193       * The link was inserted, so make changes to the editor source.
 194       *
 195       * @method _setLink
 196       * @param {EventFacade} e
 197       * @private
 198       */
 199      _setLink: function(e) {
 200          var input,
 201              value;
 202  
 203          e.preventDefault();
 204          this.getDialogue({
 205              focusAfterHide: null
 206          }).hide();
 207  
 208          input = this._content.one('.url');
 209  
 210          value = input.get('value');
 211          if (value !== '') {
 212  
 213              // We add a prefix if it is not already prefixed.
 214              value = value.trim();
 215              var expr = new RegExp(/^[a-zA-Z]*\.*\/|^#|^[a-zA-Z]*:/);
 216              if (!expr.test(value)) {
 217                  value = 'http://' + value;
 218              }
 219  
 220              // Add the link.
 221              this._setLinkOnSelection(value);
 222  
 223              this.markUpdated();
 224          }
 225      },
 226  
 227      /**
 228       * Final step setting the anchor on the selection.
 229       *
 230       * @private
 231       * @method _setLinkOnSelection
 232       * @param  {String} url URL the link will point to.
 233       * @return {Node} The added Node.
 234       */
 235      _setLinkOnSelection: function(url) {
 236          var host = this.get('host'),
 237              link,
 238              selectednode,
 239              target,
 240              anchornodes;
 241  
 242          this.editor.focus();
 243          host.setSelection(this._currentSelection);
 244  
 245          if (this._currentSelection[0].collapsed) {
 246              // Firefox cannot add links when the selection is empty so we will add it manually.
 247              link = Y.Node.create('<a>' + url + '</a>');
 248              link.setAttribute('href', url);
 249  
 250              // Add the node and select it to replicate the behaviour of execCommand.
 251              selectednode = host.insertContentAtFocusPoint(link.get('outerHTML'));
 252              host.setSelection(host.getSelectionFromNode(selectednode));
 253          } else {
 254              document.execCommand('unlink', false, null);
 255              document.execCommand('createLink', false, url);
 256  
 257              // Now set the target.
 258              selectednode = host.getSelectionParentNode();
 259          }
 260  
 261          // Note this is a document fragment and YUI doesn't like them.
 262          if (!selectednode) {
 263              return;
 264          }
 265  
 266          anchornodes = this._findSelectedAnchors(Y.one(selectednode));
 267          // Add new window attributes if requested.
 268          Y.Array.each(anchornodes, function(anchornode) {
 269              target = this._content.one('.newwindow');
 270              if (target.get('checked')) {
 271                  anchornode.setAttribute('target', '_blank');
 272              } else {
 273                  anchornode.removeAttribute('target');
 274              }
 275          }, this);
 276  
 277          return selectednode;
 278      },
 279  
 280      /**
 281       * Look up and down for the nearest anchor tags that are least partly contained in the selection.
 282       *
 283       * @method _findSelectedAnchors
 284       * @param {Node} node The node to search under for the selected anchor.
 285       * @return {Node|Boolean} The Node, or false if not found.
 286       * @private
 287       */
 288      _findSelectedAnchors: function(node) {
 289          var tagname = node.get('tagName'),
 290              hit, hits;
 291  
 292          // Direct hit.
 293          if (tagname && tagname.toLowerCase() === 'a') {
 294              return [node];
 295          }
 296  
 297          // Search down but check that each node is part of the selection.
 298          hits = [];
 299          node.all('a').each(function(n) {
 300              if (!hit && this.get('host').selectionContainsNode(n)) {
 301                  hits.push(n);
 302              }
 303          }, this);
 304          if (hits.length > 0) {
 305              return hits;
 306          }
 307          // Search up.
 308          hit = node.ancestor('a');
 309          if (hit) {
 310              return [hit];
 311          }
 312          return [];
 313      },
 314  
 315      /**
 316       * Generates the content of the dialogue.
 317       *
 318       * @method _getDialogueContent
 319       * @return {Node} Node containing the dialogue content
 320       * @private
 321       */
 322      _getDialogueContent: function() {
 323          var canShowFilepicker = this.get('host').canShowFilepicker('link'),
 324              template = Y.Handlebars.compile(TEMPLATE);
 325  
 326          this._content = Y.Node.create(template({
 327              showFilepicker: canShowFilepicker,
 328              component: COMPONENTNAME,
 329              CSS: CSS
 330          }));
 331  
 332          this._content.one('.submit').on('click', this._setLink, this);
 333          if (canShowFilepicker) {
 334              this._content.one('.openlinkbrowser').on('click', function(e) {
 335                  e.preventDefault();
 336                  this.get('host').showFilepicker('link', this._filepickerCallback, this);
 337              }, this);
 338          }
 339  
 340          return this._content;
 341      },
 342  
 343      /**
 344       * Unlinks the current selection.
 345       * If the selection is empty (e.g. the cursor is placed within a link),
 346       * then the whole link is unlinked.
 347       *
 348       * @method _unlink
 349       * @private
 350       */
 351      _unlink: function() {
 352          var host = this.get('host'),
 353              range = host.getSelection();
 354  
 355          if (range && range.length) {
 356              if (range[0].startOffset === range[0].endOffset) {
 357                  // The cursor was placed in the editor but there was no selection - select the whole parent.
 358                  var nodes = host.getSelectedNodes();
 359                  if (nodes) {
 360                      // We need to unlink each anchor individually - we cannot select a range because it may only consist of a
 361                      // fragment of an anchor. Selecting the parent would be dangerous because it may contain other links which
 362                      // would then be unlinked too.
 363                      nodes.each(function(node) {
 364                          // We need to select the whole anchor node for this to work in some browsers.
 365                          // We only need to search up because getSeletedNodes returns all Nodes in the selection.
 366                          var anchor = node.ancestor('a', true);
 367                          if (anchor) {
 368                              // Set the selection to the whole of the first anchro.
 369                              host.setSelection(host.getSelectionFromNode(anchor));
 370  
 371                              // Call the browser unlink.
 372                              document.execCommand('unlink', false, null);
 373                          }
 374                      }, this);
 375  
 376                      // And mark the text area as updated.
 377                      this.markUpdated();
 378                  }
 379              } else {
 380                  // Call the browser unlink.
 381                  document.execCommand('unlink', false, null);
 382  
 383                  // And mark the text area as updated.
 384                  this.markUpdated();
 385              }
 386          }
 387      }
 388  });
 389  
 390  
 391  }, '@VERSION@', {"requires": ["moodle-editor_atto-plugin"]});


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