[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/js/application/differential/ -> behavior-keyboard-nav.js (source)

   1  /**
   2   * @provides javelin-behavior-differential-keyboard-navigation
   3   * @requires javelin-behavior
   4   *           javelin-dom
   5   *           javelin-stratcom
   6   *           phabricator-keyboard-shortcut
   7   */
   8  
   9  JX.behavior('differential-keyboard-navigation', function(config) {
  10  
  11    var cursor = -1;
  12    var changesets;
  13  
  14    var selection_begin = null;
  15    var selection_end = null;
  16  
  17    var refreshFocus = function() {};
  18  
  19    function init() {
  20      if (changesets) {
  21        return;
  22      }
  23      changesets = JX.DOM.scry(document.body, 'div', 'differential-changeset');
  24    }
  25  
  26    function getBlocks(cursor) {
  27      // TODO: This might not be terribly fast; we can't currently memoize it
  28      // because it can change as ajax requests come in (e.g., content loads).
  29  
  30      var rows = JX.DOM.scry(changesets[cursor], 'tr');
  31      var blocks = [[changesets[cursor], changesets[cursor]]];
  32      var start = null;
  33      var type;
  34      var ii;
  35  
  36      // Don't show code blocks inside a collapsed file.
  37      var diff = JX.DOM.scry(changesets[cursor], 'table', 'differential-diff');
  38      if (diff.length == 1 && JX.Stratcom.getData(diff[0]).hidden) {
  39        return blocks;
  40      }
  41  
  42      function push() {
  43        if (start) {
  44          blocks.push([start, rows[ii - 1]]);
  45        }
  46        start = null;
  47      }
  48  
  49      for (ii = 0; ii < rows.length; ii++) {
  50        type = getRowType(rows[ii]);
  51        if (type == 'comment') {
  52          // If we see these types of rows, make a block for each one.
  53          push();
  54        }
  55        if (!type) {
  56          push();
  57        } else if (type && !start) {
  58          start = rows[ii];
  59        }
  60      }
  61      push();
  62  
  63      return blocks;
  64    }
  65  
  66    function getRowType(row) {
  67      // NOTE: Being somewhat over-general here to allow other types of objects
  68      // to be easily focused in the future (inline comments, 'show more..').
  69  
  70      if (row.className.indexOf('inline') !== -1) {
  71        return 'comment';
  72      }
  73  
  74      if (row.className.indexOf('differential-changeset') !== -1) {
  75        return 'file';
  76      }
  77  
  78      var cells = JX.DOM.scry(row, 'td');
  79  
  80      for (var ii = 0; ii < cells.length; ii++) {
  81        // NOTE: The semantic use of classnames here is for performance; don't
  82        // emulate this elsewhere since it's super terrible.
  83        if (cells[ii].className.indexOf('old') !== -1 ||
  84            cells[ii].className.indexOf('new') !== -1) {
  85          return 'change';
  86        }
  87      }
  88  
  89      return null;
  90    }
  91  
  92    function jump(manager, delta, jump_to_type) {
  93      init();
  94  
  95      if (cursor < 0) {
  96        if (delta < 0) {
  97          // If the user goes "back" without a selection, just reject the action.
  98          return;
  99        } else {
 100          cursor = 0;
 101        }
 102      }
 103  
 104      while (true) {
 105        var blocks = getBlocks(cursor);
 106        var focus;
 107        if (delta < 0) {
 108          focus = blocks.length;
 109        } else {
 110          focus = -1;
 111        }
 112  
 113        for (var ii = 0; ii < blocks.length; ii++) {
 114          if (blocks[ii][0] == selection_begin) {
 115            focus = ii;
 116            break;
 117          }
 118        }
 119  
 120        while (true) {
 121          focus += delta;
 122  
 123          if (blocks[focus]) {
 124            var row_type = getRowType(blocks[focus][0]);
 125            if (jump_to_type && row_type != jump_to_type) {
 126              continue;
 127            }
 128  
 129            selection_begin = blocks[focus][0];
 130            selection_end = blocks[focus][1];
 131  
 132            manager.scrollTo(selection_begin);
 133  
 134            refreshFocus = function() {
 135              manager.focusOn(selection_begin, selection_end);
 136            };
 137  
 138            refreshFocus();
 139  
 140            return;
 141          } else {
 142            var adjusted = (cursor + delta);
 143            if (adjusted < 0 || adjusted >= changesets.length) {
 144              // Stop cursor movement when the user reaches either end.
 145              return;
 146            }
 147            cursor = adjusted;
 148  
 149            // Break the inner loop and go to the next file.
 150            break;
 151          }
 152        }
 153      }
 154  
 155    }
 156  
 157    // When inline comments are updated, wipe out our cache of blocks since
 158    // comments may have been added or deleted.
 159    JX.Stratcom.listen(
 160      null,
 161      'differential-inline-comment-update',
 162      function() {
 163        changesets = null;
 164      });
 165    // Same thing when a file is hidden or shown; don't want to highlight
 166    // invisible code.
 167    JX.Stratcom.listen(
 168      'differential-toggle-file-toggled',
 169      null,
 170      function() {
 171        changesets = null;
 172        init();
 173        refreshFocus();
 174      });
 175  
 176    var haunt_mode = 0;
 177    function haunt() {
 178      haunt_mode = (haunt_mode + 1) % 3;
 179  
 180      var el = JX.$(config.haunt);
 181      for (var ii = 1; ii <= 2; ii++) {
 182        JX.DOM.alterClass(el, 'differential-haunt-mode-'+ii, (haunt_mode == ii));
 183      }
 184    }
 185  
 186    new JX.KeyboardShortcut('j', 'Jump to next change.')
 187      .setHandler(function(manager) {
 188        jump(manager, 1);
 189      })
 190      .register();
 191  
 192    new JX.KeyboardShortcut('k', 'Jump to previous change.')
 193      .setHandler(function(manager) {
 194        jump(manager, -1);
 195      })
 196      .register();
 197  
 198    new JX.KeyboardShortcut('J', 'Jump to next file.')
 199      .setHandler(function(manager) {
 200        jump(manager, 1, 'file');
 201      })
 202      .register();
 203  
 204    new JX.KeyboardShortcut('K', 'Jump to previous file.')
 205      .setHandler(function(manager) {
 206        jump(manager, -1, 'file');
 207      })
 208      .register();
 209  
 210    new JX.KeyboardShortcut('n', 'Jump to next inline comment.')
 211      .setHandler(function(manager) {
 212        jump(manager, 1, 'comment');
 213      })
 214      .register();
 215  
 216    new JX.KeyboardShortcut('p', 'Jump to previous inline comment.')
 217      .setHandler(function(manager) {
 218        jump(manager, -1, 'comment');
 219      })
 220      .register();
 221  
 222  
 223    new JX.KeyboardShortcut('t', 'Jump to the table of contents.')
 224      .setHandler(function(manager) {
 225        var toc = JX.$('toc');
 226        manager.scrollTo(toc);
 227      })
 228      .register();
 229  
 230    new JX.KeyboardShortcut(
 231      'h',
 232      'Collapse or expand the file display (after jump).')
 233      .setHandler(function() {
 234        if (!changesets || !changesets[cursor]) {
 235          return;
 236        }
 237        JX.Stratcom.invoke('differential-toggle-file', null, {
 238          diff: JX.DOM.scry(changesets[cursor], 'table', 'differential-diff')
 239        });
 240      })
 241      .register();
 242  
 243  
 244    function inline_op(node, op) {
 245      // nothing selected
 246      if (!node) {
 247        return;
 248      }
 249      if (!JX.DOM.scry(node, 'a', 'differential-inline-' + op)) {
 250        // No link for this operation, e.g. editing a comment you can't edit.
 251        return;
 252      }
 253  
 254      var data = {
 255        node: JX.DOM.find(node, 'div', 'differential-inline-comment'),
 256        op: op
 257      };
 258  
 259      JX.Stratcom.invoke('differential-inline-action', null, data);
 260    }
 261  
 262    new JX.KeyboardShortcut('r', 'Reply to selected inline comment.')
 263      .setHandler(function() {
 264        inline_op(selection_begin, 'reply');
 265      })
 266      .register();
 267  
 268    new JX.KeyboardShortcut('e', 'Edit selected inline comment.')
 269      .setHandler(function() {
 270        inline_op(selection_begin, 'edit');
 271      })
 272      .register();
 273  
 274    if (config.haunt) {
 275      new JX.KeyboardShortcut('z', 'Cycle comment panel haunting modes.')
 276        .setHandler(haunt)
 277        .register();
 278    }
 279  
 280  });


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1