[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/tinymce/plugins/wplink/ -> plugin.js (source)

   1  ( function( tinymce ) {
   2      tinymce.ui.Factory.add( 'WPLinkPreview', tinymce.ui.Control.extend( {
   3          url: '#',
   4          renderHtml: function() {
   5              return (
   6                  '<div id="' + this._id + '" class="wp-link-preview">' +
   7                      '<a href="' + this.url + '" target="_blank" tabindex="-1">' + this.url + '</a>' +
   8                  '</div>'
   9              );
  10          },
  11          setURL: function( url ) {
  12              var index, lastIndex;
  13  
  14              if ( this.url !== url ) {
  15                  this.url = url;
  16  
  17                  url = window.decodeURIComponent( url );
  18  
  19                  url = url.replace( /^(?:https?:)?\/\/(?:www\.)?/, '' );
  20  
  21                  if ( ( index = url.indexOf( '?' ) ) !== -1 ) {
  22                      url = url.slice( 0, index );
  23                  }
  24  
  25                  if ( ( index = url.indexOf( '#' ) ) !== -1 ) {
  26                      url = url.slice( 0, index );
  27                  }
  28  
  29                  url = url.replace( /(?:index)?\.html$/, '' );
  30  
  31                  if ( url.charAt( url.length - 1 ) === '/' ) {
  32                      url = url.slice( 0, -1 );
  33                  }
  34  
  35                  // If nothing's left (maybe the URL was just a fragment), use the whole URL.
  36                  if ( url === '' ) {
  37                      url = this.url;
  38                  }
  39  
  40                  // If the URL is longer that 40 chars, concatenate the beginning (after the domain) and ending with '...'.
  41                  if ( url.length > 40 && ( index = url.indexOf( '/' ) ) !== -1 && ( lastIndex = url.lastIndexOf( '/' ) ) !== -1 && lastIndex !== index ) {
  42                      // If the beginning + ending are shorter that 40 chars, show more of the ending.
  43                      if ( index + url.length - lastIndex < 40 ) {
  44                          lastIndex = -( 40 - ( index + 1 ) );
  45                      }
  46  
  47                      url = url.slice( 0, index + 1 ) + '\u2026' + url.slice( lastIndex );
  48                  }
  49  
  50                  tinymce.$( this.getEl().firstChild ).attr( 'href', this.url ).text( url );
  51              }
  52          }
  53      } ) );
  54  
  55      tinymce.ui.Factory.add( 'WPLinkInput', tinymce.ui.Control.extend( {
  56          renderHtml: function() {
  57              return (
  58                  '<div id="' + this._id + '" class="wp-link-input">' +
  59                      '<label for="' + this._id + '_label">' + tinymce.translate( 'Paste URL or type to search' ) + '</label><input id="' + this._id + '_label" type="text" value="" />' +
  60                      '<input type="text" style="display:none" value="" />' +
  61                  '</div>'
  62              );
  63          },
  64          setURL: function( url ) {
  65              this.getEl().firstChild.nextSibling.value = url;
  66          },
  67          getURL: function() {
  68              return tinymce.trim( this.getEl().firstChild.nextSibling.value );
  69          },
  70          getLinkText: function() {
  71              var text = this.getEl().firstChild.nextSibling.nextSibling.value;
  72  
  73              if ( ! tinymce.trim( text ) ) {
  74                  return '';
  75              }
  76  
  77              return text.replace( /[\r\n\t ]+/g, ' ' );
  78          },
  79          reset: function() {
  80              var urlInput = this.getEl().firstChild.nextSibling;
  81  
  82              urlInput.value = '';
  83              urlInput.nextSibling.value = '';
  84          }
  85      } ) );
  86  
  87      tinymce.PluginManager.add( 'wplink', function( editor ) {
  88          var toolbar;
  89          var editToolbar;
  90          var previewInstance;
  91          var inputInstance;
  92          var linkNode;
  93          var doingUndoRedo;
  94          var doingUndoRedoTimer;
  95          var $ = window.jQuery;
  96          var emailRegex = /^(mailto:)?[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,63}$/i;
  97          var urlRegex1 = /^https?:\/\/([^\s/?.#-][^\s\/?.#]*\.?)+(\/[^\s"]*)?$/i;
  98          var urlRegex2 = /^https?:\/\/[^\/]+\.[^\/]+($|\/)/i;
  99          var speak = ( typeof window.wp !== 'undefined' && window.wp.a11y && window.wp.a11y.speak ) ? window.wp.a11y.speak : function() {};
 100          var hasLinkError = false;
 101          var __ = window.wp.i18n.__;
 102          var _n = window.wp.i18n._n;
 103          var sprintf = window.wp.i18n.sprintf;
 104  
 105  		function getSelectedLink() {
 106              var href, html,
 107                  node = editor.selection.getStart(),
 108                  link = editor.dom.getParent( node, 'a[href]' );
 109  
 110              if ( ! link ) {
 111                  html = editor.selection.getContent({ format: 'raw' });
 112  
 113                  if ( html && html.indexOf( '</a>' ) !== -1 ) {
 114                      href = html.match( /href="([^">]+)"/ );
 115  
 116                      if ( href && href[1] ) {
 117                          link = editor.$( 'a[href="' + href[1] + '"]', node )[0];
 118                      }
 119  
 120                      if ( link ) {
 121                          editor.selection.select( link );
 122                      }
 123                  }
 124              }
 125  
 126              return link;
 127          }
 128  
 129  		function removePlaceholders() {
 130              editor.$( 'a' ).each( function( i, element ) {
 131                  var $element = editor.$( element );
 132  
 133                  if ( $element.attr( 'href' ) === '_wp_link_placeholder' ) {
 134                      editor.dom.remove( element, true );
 135                  } else if ( $element.attr( 'data-wplink-edit' ) ) {
 136                      $element.attr( 'data-wplink-edit', null );
 137                  }
 138              });
 139          }
 140  
 141  		function removePlaceholderStrings( content, dataAttr ) {
 142              return content.replace( /(<a [^>]+>)([\s\S]*?)<\/a>/g, function( all, tag, text ) {
 143                  if ( tag.indexOf( ' href="_wp_link_placeholder"' ) > -1 ) {
 144                      return text;
 145                  }
 146  
 147                  if ( dataAttr ) {
 148                      tag = tag.replace( / data-wplink-edit="true"/g, '' );
 149                  }
 150  
 151                  tag = tag.replace( / data-wplink-url-error="true"/g, '' );
 152  
 153                  return tag + text + '</a>';
 154              });
 155          }
 156  
 157  		function checkLink( node ) {
 158              var $link = editor.$( node );
 159              var href = $link.attr( 'href' );
 160  
 161              if ( ! href || typeof $ === 'undefined' ) {
 162                  return;
 163              }
 164  
 165              hasLinkError = false;
 166  
 167              if ( /^http/i.test( href ) && ( ! urlRegex1.test( href ) || ! urlRegex2.test( href ) ) ) {
 168                  hasLinkError = true;
 169                  $link.attr( 'data-wplink-url-error', 'true' );
 170                  speak( editor.translate( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' );
 171              } else {
 172                  $link.removeAttr( 'data-wplink-url-error' );
 173              }
 174          }
 175  
 176          editor.on( 'preinit', function() {
 177              if ( editor.wp && editor.wp._createToolbar ) {
 178                  toolbar = editor.wp._createToolbar( [
 179                      'wp_link_preview',
 180                      'wp_link_edit',
 181                      'wp_link_remove'
 182                  ], true );
 183  
 184                  var editButtons = [
 185                      'wp_link_input',
 186                      'wp_link_apply'
 187                  ];
 188  
 189                  if ( typeof window.wpLink !== 'undefined' ) {
 190                      editButtons.push( 'wp_link_advanced' );
 191                  }
 192  
 193                  editToolbar = editor.wp._createToolbar( editButtons, true );
 194  
 195                  editToolbar.on( 'show', function() {
 196                      if ( typeof window.wpLink === 'undefined' || ! window.wpLink.modalOpen ) {
 197                          window.setTimeout( function() {
 198                              var element = editToolbar.$el.find( 'input.ui-autocomplete-input' )[0],
 199                                  selection = linkNode && ( linkNode.textContent || linkNode.innerText );
 200  
 201                              if ( element ) {
 202                                  if ( ! element.value && selection && typeof window.wpLink !== 'undefined' ) {
 203                                      element.value = window.wpLink.getUrlFromSelection( selection );
 204                                  }
 205  
 206                                  if ( ! doingUndoRedo ) {
 207                                      element.focus();
 208                                      element.select();
 209                                  }
 210                              }
 211                          } );
 212                      }
 213                  } );
 214  
 215                  editToolbar.on( 'hide', function() {
 216                      if ( ! editToolbar.scrolling ) {
 217                          editor.execCommand( 'wp_link_cancel' );
 218                      }
 219                  } );
 220              }
 221          } );
 222  
 223          editor.addCommand( 'WP_Link', function() {
 224              if ( tinymce.Env.ie && tinymce.Env.ie < 10 && typeof window.wpLink !== 'undefined' ) {
 225                  window.wpLink.open( editor.id );
 226                  return;
 227              }
 228  
 229              linkNode = getSelectedLink();
 230              editToolbar.tempHide = false;
 231  
 232              if ( ! linkNode ) {
 233                  removePlaceholders();
 234                  editor.execCommand( 'mceInsertLink', false, { href: '_wp_link_placeholder' } );
 235  
 236                  linkNode = editor.$( 'a[href="_wp_link_placeholder"]' )[0];
 237                  editor.nodeChanged();
 238              }
 239  
 240              editor.dom.setAttribs( linkNode, { 'data-wplink-edit': true } );
 241          } );
 242  
 243          editor.addCommand( 'wp_link_apply', function() {
 244              if ( editToolbar.scrolling ) {
 245                  return;
 246              }
 247  
 248              var href, text;
 249  
 250              if ( linkNode ) {
 251                  href = inputInstance.getURL();
 252                  text = inputInstance.getLinkText();
 253                  editor.focus();
 254  
 255                  var parser = document.createElement( 'a' );
 256                  parser.href = href;
 257  
 258                  if ( 'javascript:' === parser.protocol || 'data:' === parser.protocol ) { // jshint ignore:line
 259                      href = '';
 260                  }
 261  
 262                  if ( ! href ) {
 263                      editor.dom.remove( linkNode, true );
 264                      return;
 265                  }
 266  
 267                  if ( ! /^(?:[a-z]+:|#|\?|\.|\/)/.test( href ) && ! emailRegex.test( href ) ) {
 268                      href = 'http://' + href;
 269                  }
 270  
 271                  editor.dom.setAttribs( linkNode, { href: href, 'data-wplink-edit': null } );
 272  
 273                  if ( ! tinymce.trim( linkNode.innerHTML ) ) {
 274                      editor.$( linkNode ).text( text || href );
 275                  }
 276  
 277                  checkLink( linkNode );
 278              }
 279  
 280              inputInstance.reset();
 281              editor.nodeChanged();
 282  
 283              // Audible confirmation message when a link has been inserted in the Editor.
 284              if ( typeof window.wpLinkL10n !== 'undefined' && ! hasLinkError ) {
 285                  speak( window.wpLinkL10n.linkInserted );
 286              }
 287          } );
 288  
 289          editor.addCommand( 'wp_link_cancel', function() {
 290              inputInstance.reset();
 291  
 292              if ( ! editToolbar.tempHide ) {
 293                  removePlaceholders();
 294              }
 295          } );
 296  
 297          editor.addCommand( 'wp_unlink', function() {
 298              editor.execCommand( 'unlink' );
 299              editToolbar.tempHide = false;
 300              editor.execCommand( 'wp_link_cancel' );
 301          } );
 302  
 303          // WP default shortcuts.
 304          editor.addShortcut( 'access+a', '', 'WP_Link' );
 305          editor.addShortcut( 'access+s', '', 'wp_unlink' );
 306          // The "de-facto standard" shortcut, see #27305.
 307          editor.addShortcut( 'meta+k', '', 'WP_Link' );
 308  
 309          editor.addButton( 'link', {
 310              icon: 'link',
 311              tooltip: 'Insert/edit link',
 312              cmd: 'WP_Link',
 313              stateSelector: 'a[href]'
 314          });
 315  
 316          editor.addButton( 'unlink', {
 317              icon: 'unlink',
 318              tooltip: 'Remove link',
 319              cmd: 'unlink'
 320          });
 321  
 322          editor.addMenuItem( 'link', {
 323              icon: 'link',
 324              text: 'Insert/edit link',
 325              cmd: 'WP_Link',
 326              stateSelector: 'a[href]',
 327              context: 'insert',
 328              prependToContext: true
 329          });
 330  
 331          editor.on( 'pastepreprocess', function( event ) {
 332              var pastedStr = event.content,
 333                  regExp = /^(?:https?:)?\/\/\S+$/i;
 334  
 335              if ( ! editor.selection.isCollapsed() && ! regExp.test( editor.selection.getContent() ) ) {
 336                  pastedStr = pastedStr.replace( /<[^>]+>/g, '' );
 337                  pastedStr = tinymce.trim( pastedStr );
 338  
 339                  if ( regExp.test( pastedStr ) ) {
 340                      editor.execCommand( 'mceInsertLink', false, {
 341                          href: editor.dom.decode( pastedStr )
 342                      } );
 343  
 344                      event.preventDefault();
 345                  }
 346              }
 347          } );
 348  
 349          // Remove any remaining placeholders on saving.
 350          editor.on( 'savecontent', function( event ) {
 351              event.content = removePlaceholderStrings( event.content, true );
 352          });
 353  
 354          // Prevent adding undo levels on inserting link placeholder.
 355          editor.on( 'BeforeAddUndo', function( event ) {
 356              if ( event.lastLevel && event.lastLevel.content && event.level.content &&
 357                  event.lastLevel.content === removePlaceholderStrings( event.level.content ) ) {
 358  
 359                  event.preventDefault();
 360              }
 361          });
 362  
 363          // When doing undo and redo with keyboard shortcuts (Ctrl|Cmd+Z, Ctrl|Cmd+Shift+Z, Ctrl|Cmd+Y),
 364          // set a flag to not focus the inline dialog. The editor has to remain focused so the users can do consecutive undo/redo.
 365          editor.on( 'keydown', function( event ) {
 366              if ( event.keyCode === 27 ) { // Esc
 367                  editor.execCommand( 'wp_link_cancel' );
 368              }
 369  
 370              if ( event.altKey || ( tinymce.Env.mac && ( ! event.metaKey || event.ctrlKey ) ) ||
 371                  ( ! tinymce.Env.mac && ! event.ctrlKey ) ) {
 372  
 373                  return;
 374              }
 375  
 376              if ( event.keyCode === 89 || event.keyCode === 90 ) { // Y or Z
 377                  doingUndoRedo = true;
 378  
 379                  window.clearTimeout( doingUndoRedoTimer );
 380                  doingUndoRedoTimer = window.setTimeout( function() {
 381                      doingUndoRedo = false;
 382                  }, 500 );
 383              }
 384          } );
 385  
 386          editor.addButton( 'wp_link_preview', {
 387              type: 'WPLinkPreview',
 388              onPostRender: function() {
 389                  previewInstance = this;
 390              }
 391          } );
 392  
 393          editor.addButton( 'wp_link_input', {
 394              type: 'WPLinkInput',
 395              onPostRender: function() {
 396                  var element = this.getEl(),
 397                      input = element.firstChild.nextSibling,
 398                      $input, cache, last;
 399  
 400                  inputInstance = this;
 401  
 402                  if ( $ && $.ui && $.ui.autocomplete ) {
 403                      $input = $( input );
 404  
 405                      $input.on( 'keydown', function() {
 406                          $input.removeAttr( 'aria-activedescendant' );
 407                      } )
 408                      .autocomplete( {
 409                          source: function( request, response ) {
 410                              if ( last === request.term ) {
 411                                  response( cache );
 412                                  return;
 413                              }
 414  
 415                              if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) {
 416                                  return response();
 417                              }
 418  
 419                              $.post( window.ajaxurl, {
 420                                  action: 'wp-link-ajax',
 421                                  page: 1,
 422                                  search: request.term,
 423                                  _ajax_linking_nonce: $( '#_ajax_linking_nonce' ).val()
 424                              }, function( data ) {
 425                                  cache = data;
 426                                  response( data );
 427                              }, 'json' );
 428  
 429                              last = request.term;
 430                          },
 431                          focus: function( event, ui ) {
 432                              $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID );
 433                              /*
 434                               * Don't empty the URL input field, when using the arrow keys to
 435                               * highlight items. See api.jqueryui.com/autocomplete/#event-focus
 436                               */
 437                              event.preventDefault();
 438                          },
 439                          select: function( event, ui ) {
 440                              $input.val( ui.item.permalink );
 441                              $( element.firstChild.nextSibling.nextSibling ).val( ui.item.title );
 442  
 443                              if ( 9 === event.keyCode && typeof window.wpLinkL10n !== 'undefined' ) {
 444                                  // Audible confirmation message when a link has been selected.
 445                                  speak( window.wpLinkL10n.linkSelected );
 446                              }
 447  
 448                              return false;
 449                          },
 450                          open: function() {
 451                              $input.attr( 'aria-expanded', 'true' );
 452                              editToolbar.blockHide = true;
 453                          },
 454                          close: function() {
 455                              $input.attr( 'aria-expanded', 'false' );
 456                              editToolbar.blockHide = false;
 457                          },
 458                          minLength: 2,
 459                          position: {
 460                              my: 'left top+2'
 461                          },
 462                          messages: {
 463                              noResults: __( 'No results found.' ) ,
 464                              results: function( number ) {
 465                                  return sprintf(
 466                                      /* translators: %d: Number of search results found. */
 467                                      _n(
 468                                          '%d result found. Use up and down arrow keys to navigate.',
 469                                          '%d results found. Use up and down arrow keys to navigate.',
 470                                          number
 471                                      ),
 472                                      number
 473                                  );
 474                              }
 475                          }
 476                      } ).autocomplete( 'instance' )._renderItem = function( ul, item ) {
 477                          var fallbackTitle = ( typeof window.wpLinkL10n !== 'undefined' ) ? window.wpLinkL10n.noTitle : '',
 478                              title = item.title ? item.title : fallbackTitle;
 479  
 480                          return $( '<li role="option" id="mce-wp-autocomplete-' + item.ID + '">' )
 481                          .append( '<span>' + title + '</span>&nbsp;<span class="wp-editor-float-right">' + item.info + '</span>' )
 482                          .appendTo( ul );
 483                      };
 484  
 485                      $input.attr( {
 486                          'role': 'combobox',
 487                          'aria-autocomplete': 'list',
 488                          'aria-expanded': 'false',
 489                          'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' )
 490                      } )
 491                      .on( 'focus', function() {
 492                          var inputValue = $input.val();
 493                          /*
 494                           * Don't trigger a search if the URL field already has a link or is empty.
 495                           * Also, avoids screen readers announce `No search results`.
 496                           */
 497                          if ( inputValue && ! /^https?:/.test( inputValue ) ) {
 498                              $input.autocomplete( 'search' );
 499                          }
 500                      } )
 501                      // Returns a jQuery object containing the menu element.
 502                      .autocomplete( 'widget' )
 503                          .addClass( 'wplink-autocomplete' )
 504                          .attr( 'role', 'listbox' )
 505                          .removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI.
 506                          /*
 507                           * Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301.
 508                           * The `menufocus` and `menublur` events are the same events used to add and remove
 509                           * the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget.
 510                           */
 511                          .on( 'menufocus', function( event, ui ) {
 512                              ui.item.attr( 'aria-selected', 'true' );
 513                          })
 514                          .on( 'menublur', function() {
 515                              /*
 516                               * The `menublur` event returns an object where the item is `null`
 517                               * so we need to find the active item with other means.
 518                               */
 519                              $( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' );
 520                          });
 521                  }
 522  
 523                  tinymce.$( input ).on( 'keydown', function( event ) {
 524                      if ( event.keyCode === 13 ) {
 525                          editor.execCommand( 'wp_link_apply' );
 526                          event.preventDefault();
 527                      }
 528                  } );
 529              }
 530          } );
 531  
 532          editor.on( 'wptoolbar', function( event ) {
 533              var linkNode = editor.dom.getParent( event.element, 'a' ),
 534                  $linkNode, href, edit;
 535  
 536              if ( typeof window.wpLink !== 'undefined' && window.wpLink.modalOpen ) {
 537                  editToolbar.tempHide = true;
 538                  return;
 539              }
 540  
 541              editToolbar.tempHide = false;
 542  
 543              if ( linkNode ) {
 544                  $linkNode = editor.$( linkNode );
 545                  href = $linkNode.attr( 'href' );
 546                  edit = $linkNode.attr( 'data-wplink-edit' );
 547  
 548                  if ( href === '_wp_link_placeholder' || edit ) {
 549                      if ( href !== '_wp_link_placeholder' && ! inputInstance.getURL() ) {
 550                          inputInstance.setURL( href );
 551                      }
 552  
 553                      event.element = linkNode;
 554                      event.toolbar = editToolbar;
 555                  } else if ( href && ! $linkNode.find( 'img' ).length ) {
 556                      previewInstance.setURL( href );
 557                      event.element = linkNode;
 558                      event.toolbar = toolbar;
 559  
 560                      if ( $linkNode.attr( 'data-wplink-url-error' ) === 'true' ) {
 561                          toolbar.$el.find( '.wp-link-preview a' ).addClass( 'wplink-url-error' );
 562                      } else {
 563                          toolbar.$el.find( '.wp-link-preview a' ).removeClass( 'wplink-url-error' );
 564                          hasLinkError = false;
 565                      }
 566                  }
 567              } else if ( editToolbar.visible() ) {
 568                  editor.execCommand( 'wp_link_cancel' );
 569              }
 570          } );
 571  
 572          editor.addButton( 'wp_link_edit', {
 573              tooltip: 'Edit|button', // '|button' is not displayed, only used for context.
 574              icon: 'dashicon dashicons-edit',
 575              cmd: 'WP_Link'
 576          } );
 577  
 578          editor.addButton( 'wp_link_remove', {
 579              tooltip: 'Remove link',
 580              icon: 'dashicon dashicons-editor-unlink',
 581              cmd: 'wp_unlink'
 582          } );
 583  
 584          editor.addButton( 'wp_link_advanced', {
 585              tooltip: 'Link options',
 586              icon: 'dashicon dashicons-admin-generic',
 587              onclick: function() {
 588                  if ( typeof window.wpLink !== 'undefined' ) {
 589                      var url = inputInstance.getURL() || null,
 590                          text = inputInstance.getLinkText() || null;
 591  
 592                      window.wpLink.open( editor.id, url, text );
 593  
 594                      editToolbar.tempHide = true;
 595                      editToolbar.hide();
 596                  }
 597              }
 598          } );
 599  
 600          editor.addButton( 'wp_link_apply', {
 601              tooltip: 'Apply',
 602              icon: 'dashicon dashicons-editor-break',
 603              cmd: 'wp_link_apply',
 604              classes: 'widget btn primary'
 605          } );
 606  
 607          return {
 608              close: function() {
 609                  editToolbar.tempHide = false;
 610                  editor.execCommand( 'wp_link_cancel' );
 611              },
 612              checkLink: checkLink
 613          };
 614      } );
 615  } )( window.tinymce );


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref