[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-admin/js/ -> code-editor.js (source)

   1  /**
   2   * @output wp-admin/js/code-editor.js
   3   */
   4  
   5  if ( 'undefined' === typeof window.wp ) {
   6      /**
   7       * @namespace wp
   8       */
   9      window.wp = {};
  10  }
  11  if ( 'undefined' === typeof window.wp.codeEditor ) {
  12      /**
  13       * @namespace wp.codeEditor
  14       */
  15      window.wp.codeEditor = {};
  16  }
  17  
  18  ( function( $, wp ) {
  19      'use strict';
  20  
  21      /**
  22       * Default settings for code editor.
  23       *
  24       * @since 4.9.0
  25       * @type {object}
  26       */
  27      wp.codeEditor.defaultSettings = {
  28          codemirror: {},
  29          csslint: {},
  30          htmlhint: {},
  31          jshint: {},
  32          onTabNext: function() {},
  33          onTabPrevious: function() {},
  34          onChangeLintingErrors: function() {},
  35          onUpdateErrorNotice: function() {}
  36      };
  37  
  38      /**
  39       * Configure linting.
  40       *
  41       * @param {CodeMirror} editor - Editor.
  42       * @param {object}     settings - Code editor settings.
  43       * @param {object}     settings.codeMirror - Settings for CodeMirror.
  44       * @param {Function}   settings.onChangeLintingErrors - Callback for when there are changes to linting errors.
  45       * @param {Function}   settings.onUpdateErrorNotice - Callback to update error notice.
  46       *
  47       * @returns {void}
  48       */
  49  	function configureLinting( editor, settings ) { // eslint-disable-line complexity
  50          var currentErrorAnnotations = [], previouslyShownErrorAnnotations = [];
  51  
  52          /**
  53           * Call the onUpdateErrorNotice if there are new errors to show.
  54           *
  55           * @returns {void}
  56           */
  57  		function updateErrorNotice() {
  58              if ( settings.onUpdateErrorNotice && ! _.isEqual( currentErrorAnnotations, previouslyShownErrorAnnotations ) ) {
  59                  settings.onUpdateErrorNotice( currentErrorAnnotations, editor );
  60                  previouslyShownErrorAnnotations = currentErrorAnnotations;
  61              }
  62          }
  63  
  64          /**
  65           * Get lint options.
  66           *
  67           * @returns {object} Lint options.
  68           */
  69  		function getLintOptions() { // eslint-disable-line complexity
  70              var options = editor.getOption( 'lint' );
  71  
  72              if ( ! options ) {
  73                  return false;
  74              }
  75  
  76              if ( true === options ) {
  77                  options = {};
  78              } else if ( _.isObject( options ) ) {
  79                  options = $.extend( {}, options );
  80              }
  81  
  82              // Note that rules must be sent in the "deprecated" lint.options property to prevent linter from complaining about unrecognized options. See <https://github.com/codemirror/CodeMirror/pull/4944>.
  83              if ( ! options.options ) {
  84                  options.options = {};
  85              }
  86  
  87              // Configure JSHint.
  88              if ( 'javascript' === settings.codemirror.mode && settings.jshint ) {
  89                  $.extend( options.options, settings.jshint );
  90              }
  91  
  92              // Configure CSSLint.
  93              if ( 'css' === settings.codemirror.mode && settings.csslint ) {
  94                  $.extend( options.options, settings.csslint );
  95              }
  96  
  97              // Configure HTMLHint.
  98              if ( 'htmlmixed' === settings.codemirror.mode && settings.htmlhint ) {
  99                  options.options.rules = $.extend( {}, settings.htmlhint );
 100  
 101                  if ( settings.jshint ) {
 102                      options.options.rules.jshint = settings.jshint;
 103                  }
 104                  if ( settings.csslint ) {
 105                      options.options.rules.csslint = settings.csslint;
 106                  }
 107              }
 108  
 109              // Wrap the onUpdateLinting CodeMirror event to route to onChangeLintingErrors and onUpdateErrorNotice.
 110              options.onUpdateLinting = (function( onUpdateLintingOverridden ) {
 111                  return function( annotations, annotationsSorted, cm ) {
 112                      var errorAnnotations = _.filter( annotations, function( annotation ) {
 113                          return 'error' === annotation.severity;
 114                      } );
 115  
 116                      if ( onUpdateLintingOverridden ) {
 117                          onUpdateLintingOverridden.apply( annotations, annotationsSorted, cm );
 118                      }
 119  
 120                      // Skip if there are no changes to the errors.
 121                      if ( _.isEqual( errorAnnotations, currentErrorAnnotations ) ) {
 122                          return;
 123                      }
 124  
 125                      currentErrorAnnotations = errorAnnotations;
 126  
 127                      if ( settings.onChangeLintingErrors ) {
 128                          settings.onChangeLintingErrors( errorAnnotations, annotations, annotationsSorted, cm );
 129                      }
 130  
 131                      /*
 132                       * Update notifications when the editor is not focused to prevent error message
 133                       * from overwhelming the user during input, unless there are now no errors or there
 134                       * were previously errors shown. In these cases, update immediately so they can know
 135                       * that they fixed the errors.
 136                       */
 137                      if ( ! editor.state.focused || 0 === currentErrorAnnotations.length || previouslyShownErrorAnnotations.length > 0 ) {
 138                          updateErrorNotice();
 139                      }
 140                  };
 141              })( options.onUpdateLinting );
 142  
 143              return options;
 144          }
 145  
 146          editor.setOption( 'lint', getLintOptions() );
 147  
 148          // Keep lint options populated.
 149          editor.on( 'optionChange', function( cm, option ) {
 150              var options, gutters, gutterName = 'CodeMirror-lint-markers';
 151              if ( 'lint' !== option ) {
 152                  return;
 153              }
 154              gutters = editor.getOption( 'gutters' ) || [];
 155              options = editor.getOption( 'lint' );
 156              if ( true === options ) {
 157                  if ( ! _.contains( gutters, gutterName ) ) {
 158                      editor.setOption( 'gutters', [ gutterName ].concat( gutters ) );
 159                  }
 160                  editor.setOption( 'lint', getLintOptions() ); // Expand to include linting options.
 161              } else if ( ! options ) {
 162                  editor.setOption( 'gutters', _.without( gutters, gutterName ) );
 163              }
 164  
 165              // Force update on error notice to show or hide.
 166              if ( editor.getOption( 'lint' ) ) {
 167                  editor.performLint();
 168              } else {
 169                  currentErrorAnnotations = [];
 170                  updateErrorNotice();
 171              }
 172          } );
 173  
 174          // Update error notice when leaving the editor.
 175          editor.on( 'blur', updateErrorNotice );
 176  
 177          // Work around hint selection with mouse causing focus to leave editor.
 178          editor.on( 'startCompletion', function() {
 179              editor.off( 'blur', updateErrorNotice );
 180          } );
 181          editor.on( 'endCompletion', function() {
 182              var editorRefocusWait = 500;
 183              editor.on( 'blur', updateErrorNotice );
 184  
 185              // Wait for editor to possibly get re-focused after selection.
 186              _.delay( function() {
 187                  if ( ! editor.state.focused ) {
 188                      updateErrorNotice();
 189                  }
 190              }, editorRefocusWait );
 191          });
 192  
 193          /*
 194           * Make sure setting validities are set if the user tries to click Publish
 195           * while an autocomplete dropdown is still open. The Customizer will block
 196           * saving when a setting has an error notifications on it. This is only
 197           * necessary for mouse interactions because keyboards will have already
 198           * blurred the field and cause onUpdateErrorNotice to have already been
 199           * called.
 200           */
 201          $( document.body ).on( 'mousedown', function( event ) {
 202              if ( editor.state.focused && ! $.contains( editor.display.wrapper, event.target ) && ! $( event.target ).hasClass( 'CodeMirror-hint' ) ) {
 203                  updateErrorNotice();
 204              }
 205          });
 206      }
 207  
 208      /**
 209       * Configure tabbing.
 210       *
 211       * @param {CodeMirror} codemirror - Editor.
 212       * @param {object}     settings - Code editor settings.
 213       * @param {object}     settings.codeMirror - Settings for CodeMirror.
 214       * @param {Function}   settings.onTabNext - Callback to handle tabbing to the next tabbable element.
 215       * @param {Function}   settings.onTabPrevious - Callback to handle tabbing to the previous tabbable element.
 216       *
 217       * @returns {void}
 218       */
 219  	function configureTabbing( codemirror, settings ) {
 220          var $textarea = $( codemirror.getTextArea() );
 221  
 222          codemirror.on( 'blur', function() {
 223              $textarea.data( 'next-tab-blurs', false );
 224          });
 225          codemirror.on( 'keydown', function onKeydown( editor, event ) {
 226              var tabKeyCode = 9, escKeyCode = 27;
 227  
 228              // Take note of the ESC keypress so that the next TAB can focus outside the editor.
 229              if ( escKeyCode === event.keyCode ) {
 230                  $textarea.data( 'next-tab-blurs', true );
 231                  return;
 232              }
 233  
 234              // Short-circuit if tab key is not being pressed or the tab key press should move focus.
 235              if ( tabKeyCode !== event.keyCode || ! $textarea.data( 'next-tab-blurs' ) ) {
 236                  return;
 237              }
 238  
 239              // Focus on previous or next focusable item.
 240              if ( event.shiftKey ) {
 241                  settings.onTabPrevious( codemirror, event );
 242              } else {
 243                  settings.onTabNext( codemirror, event );
 244              }
 245  
 246              // Reset tab state.
 247              $textarea.data( 'next-tab-blurs', false );
 248  
 249              // Prevent tab character from being added.
 250              event.preventDefault();
 251          });
 252      }
 253  
 254      /**
 255       * @typedef {object} wp.codeEditor~CodeEditorInstance
 256       * @property {object} settings - The code editor settings.
 257       * @property {CodeMirror} codemirror - The CodeMirror instance.
 258       */
 259  
 260      /**
 261       * Initialize Code Editor (CodeMirror) for an existing textarea.
 262       *
 263       * @since 4.9.0
 264       *
 265       * @param {string|jQuery|Element} textarea - The HTML id, jQuery object, or DOM Element for the textarea that is used for the editor.
 266       * @param {object}                [settings] - Settings to override defaults.
 267       * @param {Function}              [settings.onChangeLintingErrors] - Callback for when the linting errors have changed.
 268       * @param {Function}              [settings.onUpdateErrorNotice] - Callback for when error notice should be displayed.
 269       * @param {Function}              [settings.onTabPrevious] - Callback to handle tabbing to the previous tabbable element.
 270       * @param {Function}              [settings.onTabNext] - Callback to handle tabbing to the next tabbable element.
 271       * @param {object}                [settings.codemirror] - Options for CodeMirror.
 272       * @param {object}                [settings.csslint] - Rules for CSSLint.
 273       * @param {object}                [settings.htmlhint] - Rules for HTMLHint.
 274       * @param {object}                [settings.jshint] - Rules for JSHint.
 275       *
 276       * @returns {CodeEditorInstance} Instance.
 277       */
 278      wp.codeEditor.initialize = function initialize( textarea, settings ) {
 279          var $textarea, codemirror, instanceSettings, instance;
 280          if ( 'string' === typeof textarea ) {
 281              $textarea = $( '#' + textarea );
 282          } else {
 283              $textarea = $( textarea );
 284          }
 285  
 286          instanceSettings = $.extend( {}, wp.codeEditor.defaultSettings, settings );
 287          instanceSettings.codemirror = $.extend( {}, instanceSettings.codemirror );
 288  
 289          codemirror = wp.CodeMirror.fromTextArea( $textarea[0], instanceSettings.codemirror );
 290  
 291          configureLinting( codemirror, instanceSettings );
 292  
 293          instance = {
 294              settings: instanceSettings,
 295              codemirror: codemirror
 296          };
 297  
 298          if ( codemirror.showHint ) {
 299              codemirror.on( 'keyup', function( editor, event ) { // eslint-disable-line complexity
 300                  var shouldAutocomplete, isAlphaKey = /^[a-zA-Z]$/.test( event.key ), lineBeforeCursor, innerMode, token;
 301                  if ( codemirror.state.completionActive && isAlphaKey ) {
 302                      return;
 303                  }
 304  
 305                  // Prevent autocompletion in string literals or comments.
 306                  token = codemirror.getTokenAt( codemirror.getCursor() );
 307                  if ( 'string' === token.type || 'comment' === token.type ) {
 308                      return;
 309                  }
 310  
 311                  innerMode = wp.CodeMirror.innerMode( codemirror.getMode(), token.state ).mode.name;
 312                  lineBeforeCursor = codemirror.doc.getLine( codemirror.doc.getCursor().line ).substr( 0, codemirror.doc.getCursor().ch );
 313                  if ( 'html' === innerMode || 'xml' === innerMode ) {
 314                      shouldAutocomplete =
 315                          '<' === event.key ||
 316                          '/' === event.key && 'tag' === token.type ||
 317                          isAlphaKey && 'tag' === token.type ||
 318                          isAlphaKey && 'attribute' === token.type ||
 319                          '=' === token.string && token.state.htmlState && token.state.htmlState.tagName;
 320                  } else if ( 'css' === innerMode ) {
 321                      shouldAutocomplete =
 322                          isAlphaKey ||
 323                          ':' === event.key ||
 324                          ' ' === event.key && /:\s+$/.test( lineBeforeCursor );
 325                  } else if ( 'javascript' === innerMode ) {
 326                      shouldAutocomplete = isAlphaKey || '.' === event.key;
 327                  } else if ( 'clike' === innerMode && 'php' === codemirror.options.mode ) {
 328                      shouldAutocomplete = 'keyword' === token.type || 'variable' === token.type;
 329                  }
 330                  if ( shouldAutocomplete ) {
 331                      codemirror.showHint( { completeSingle: false } );
 332                  }
 333              });
 334          }
 335  
 336          // Facilitate tabbing out of the editor.
 337          configureTabbing( codemirror, settings );
 338  
 339          return instance;
 340      };
 341  
 342  })( window.jQuery, window.wp );


Generated: Wed Sep 18 01:00:03 2019 Cross-referenced by PHPXref 0.7.1