[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


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