github.com/sean-/go@v0.0.0-20151219100004-97f854cd7bb6/doc/codewalk/codewalk.js (about)

     1  // Copyright 2010 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /**
     6   * A class to hold information about the Codewalk Viewer.
     7   * @param {jQuery} context The top element in whose context the viewer should
     8   *     operate.  It will not touch any elements above this one.
     9   * @constructor
    10   */
    11   var CodewalkViewer = function(context) {
    12    this.context = context;
    13  
    14    /**
    15     * The div that contains all of the comments and their controls.
    16     */
    17    this.commentColumn = this.context.find('#comment-column');
    18  
    19    /**
    20     * The div that contains the comments proper.
    21     */
    22    this.commentArea = this.context.find('#comment-area');
    23  
    24    /**
    25     * The div that wraps the iframe with the code, as well as the drop down menu
    26     * listing the different files.
    27     * @type {jQuery}
    28     */
    29    this.codeColumn = this.context.find('#code-column');
    30  
    31    /**
    32     * The div that contains the code but excludes the options strip.
    33     * @type {jQuery}
    34     */
    35    this.codeArea = this.context.find('#code-area');
    36  
    37    /**
    38     * The iframe that holds the code (from Sourcerer).
    39     * @type {jQuery}
    40     */
    41    this.codeDisplay = this.context.find('#code-display');
    42  
    43    /**
    44     * The overlaid div used as a grab handle for sizing the code/comment panes.
    45     * @type {jQuery}
    46     */
    47    this.sizer = this.context.find('#sizer');
    48  
    49    /**
    50     * The full-screen overlay that ensures we don't lose track of the mouse
    51     * while dragging.
    52     * @type {jQuery}
    53     */
    54    this.overlay = this.context.find('#overlay');
    55  
    56    /**
    57     * The hidden input field that we use to hold the focus so that we can detect
    58     * shortcut keypresses.
    59     * @type {jQuery}
    60     */
    61    this.shortcutInput = this.context.find('#shortcut-input');
    62  
    63    /**
    64     * The last comment that was selected.
    65     * @type {jQuery}
    66     */
    67    this.lastSelected = null;
    68  };
    69  
    70  /**
    71   * Minimum width of the comments or code pane, in pixels.
    72   * @type {number}
    73   */
    74  CodewalkViewer.MIN_PANE_WIDTH = 200;
    75  
    76  /**
    77   * Navigate the code iframe to the given url and update the code popout link.
    78   * @param {string} url The target URL.
    79   * @param {Object} opt_window Window dependency injection for testing only.
    80   */
    81  CodewalkViewer.prototype.navigateToCode = function(url, opt_window) {
    82    if (!opt_window) opt_window = window;
    83    // Each iframe is represented by two distinct objects in the DOM:  an iframe
    84    // object and a window object.  These do not expose the same capabilities.
    85    // Here we need to get the window representation to get the location member,
    86    // so we access it directly through window[] since jQuery returns the iframe
    87    // representation.
    88    // We replace location rather than set so as not to create a history for code
    89    // navigation.
    90    opt_window['code-display'].location.replace(url);
    91    var k = url.indexOf('&');
    92    if (k != -1) url = url.slice(0, k);
    93    k = url.indexOf('fileprint=');
    94    if (k != -1) url = url.slice(k+10, url.length);
    95    this.context.find('#code-popout-link').attr('href', url);
    96  };
    97  
    98  /**
    99   * Selects the first comment from the list and forces a refresh of the code
   100   * view.
   101   */
   102  CodewalkViewer.prototype.selectFirstComment = function() {
   103    // TODO(rsc): handle case where there are no comments
   104    var firstSourcererLink = this.context.find('.comment:first');
   105    this.changeSelectedComment(firstSourcererLink);
   106  };
   107  
   108  /**
   109   * Sets the target on all links nested inside comments to be _blank.
   110   */
   111  CodewalkViewer.prototype.targetCommentLinksAtBlank = function() {
   112    this.context.find('.comment a[href], #description a[href]').each(function() {
   113      if (!this.target) this.target = '_blank';
   114    });
   115  };
   116  
   117  /**
   118   * Installs event handlers for all the events we care about.
   119   */
   120  CodewalkViewer.prototype.installEventHandlers = function() {
   121    var self = this;
   122  
   123    this.context.find('.comment')
   124        .click(function(event) {
   125          if (jQuery(event.target).is('a[href]')) return true;
   126          self.changeSelectedComment(jQuery(this));
   127          return false;
   128        });
   129  
   130    this.context.find('#code-selector')
   131        .change(function() {self.navigateToCode(jQuery(this).val());});
   132  
   133    this.context.find('#description-table .quote-feet.setting')
   134        .click(function() {self.toggleDescription(jQuery(this)); return false;});
   135  
   136    this.sizer
   137        .mousedown(function(ev) {self.startSizerDrag(ev); return false;});
   138    this.overlay
   139        .mouseup(function(ev) {self.endSizerDrag(ev); return false;})
   140        .mousemove(function(ev) {self.handleSizerDrag(ev); return false;});
   141  
   142    this.context.find('#prev-comment')
   143        .click(function() {
   144            self.changeSelectedComment(self.lastSelected.prev()); return false;
   145        });
   146  
   147    this.context.find('#next-comment')
   148        .click(function() {
   149            self.changeSelectedComment(self.lastSelected.next()); return false;
   150        });
   151  
   152    // Workaround for Firefox 2 and 3, which steal focus from the main document
   153    // whenever the iframe content is (re)loaded.  The input field is not shown,
   154    // but is a way for us to bring focus back to a place where we can detect
   155    // keypresses.
   156    this.context.find('#code-display')
   157        .load(function(ev) {self.shortcutInput.focus();});
   158  
   159    jQuery(document).keypress(function(ev) {
   160      switch(ev.which) {
   161        case 110:  // 'n'
   162            self.changeSelectedComment(self.lastSelected.next());
   163            return false;
   164        case 112:  // 'p'
   165            self.changeSelectedComment(self.lastSelected.prev());
   166            return false;
   167        default:  // ignore
   168      }
   169    });
   170  
   171    window.onresize = function() {self.updateHeight();};
   172  };
   173  
   174  /**
   175   * Starts dragging the pane sizer.
   176   * @param {Object} ev The mousedown event that started us dragging.
   177   */
   178  CodewalkViewer.prototype.startSizerDrag = function(ev) {
   179    this.initialCodeWidth = this.codeColumn.width();
   180    this.initialCommentsWidth = this.commentColumn.width();
   181    this.initialMouseX = ev.pageX;
   182    this.overlay.show();
   183  };
   184  
   185  /**
   186   * Handles dragging the pane sizer.
   187   * @param {Object} ev The mousemove event updating dragging position.
   188   */
   189  CodewalkViewer.prototype.handleSizerDrag = function(ev) {
   190    var delta = ev.pageX - this.initialMouseX;
   191    if (this.codeColumn.is('.right')) delta = -delta;
   192    var proposedCodeWidth = this.initialCodeWidth + delta;
   193    var proposedCommentWidth = this.initialCommentsWidth - delta;
   194    var mw = CodewalkViewer.MIN_PANE_WIDTH;
   195    if (proposedCodeWidth < mw) delta = mw - this.initialCodeWidth;
   196    if (proposedCommentWidth < mw) delta = this.initialCommentsWidth - mw;
   197    proposedCodeWidth = this.initialCodeWidth + delta;
   198    proposedCommentWidth = this.initialCommentsWidth - delta;
   199    // If window is too small, don't even try to resize.
   200    if (proposedCodeWidth < mw || proposedCommentWidth < mw) return;
   201    this.codeColumn.width(proposedCodeWidth);
   202    this.commentColumn.width(proposedCommentWidth);
   203    this.options.codeWidth = parseInt(
   204        this.codeColumn.width() /
   205        (this.codeColumn.width() + this.commentColumn.width()) * 100);
   206    this.context.find('#code-column-width').text(this.options.codeWidth + '%');
   207  };
   208  
   209  /**
   210   * Ends dragging the pane sizer.
   211   * @param {Object} ev The mouseup event that caused us to stop dragging.
   212   */
   213  CodewalkViewer.prototype.endSizerDrag = function(ev) {
   214    this.overlay.hide();
   215    this.updateHeight();
   216  };
   217  
   218  /**
   219   * Toggles the Codewalk description between being shown and hidden.
   220   * @param {jQuery} target The target that was clicked to trigger this function.
   221   */
   222  CodewalkViewer.prototype.toggleDescription = function(target) {
   223    var description = this.context.find('#description');
   224    description.toggle();
   225    target.find('span').text(description.is(':hidden') ? 'show' : 'hide');
   226    this.updateHeight();
   227  };
   228  
   229  /**
   230   * Changes the side of the window on which the code is shown and saves the
   231   * setting in a cookie.
   232   * @param {string?} codeSide The side on which the code should be, either
   233   *     'left' or 'right'.
   234   */
   235  CodewalkViewer.prototype.changeCodeSide = function(codeSide) {
   236    var commentSide = codeSide == 'left' ? 'right' : 'left';
   237    this.context.find('#set-code-' + codeSide).addClass('selected');
   238    this.context.find('#set-code-' + commentSide).removeClass('selected');
   239    // Remove previous side class and add new one.
   240    this.codeColumn.addClass(codeSide).removeClass(commentSide);
   241    this.commentColumn.addClass(commentSide).removeClass(codeSide);
   242    this.sizer.css(codeSide, 'auto').css(commentSide, 0);
   243    this.options.codeSide = codeSide;
   244  };
   245  
   246  /**
   247   * Adds selected class to newly selected comment, removes selected style from
   248   * previously selected comment, changes drop down options so that the correct
   249   * file is selected, and updates the code popout link.
   250   * @param {jQuery} target The target that was clicked to trigger this function.
   251   */
   252  CodewalkViewer.prototype.changeSelectedComment = function(target) {
   253    var currentFile = target.find('.comment-link').attr('href');
   254    if (!currentFile) return;
   255  
   256    if (!(this.lastSelected && this.lastSelected.get(0) === target.get(0))) {
   257      if (this.lastSelected) this.lastSelected.removeClass('selected');
   258      target.addClass('selected');
   259      this.lastSelected = target;
   260      var targetTop = target.position().top;
   261      var parentTop = target.parent().position().top;
   262      if (targetTop + target.height() > parentTop + target.parent().height() ||
   263          targetTop < parentTop) {
   264        var delta = targetTop - parentTop;
   265        target.parent().animate(
   266            {'scrollTop': target.parent().scrollTop() + delta},
   267            Math.max(delta / 2, 200), 'swing');
   268      }
   269      var fname = currentFile.match(/(?:select=|fileprint=)\/[^&]+/)[0];
   270      fname = fname.slice(fname.indexOf('=')+2, fname.length);
   271      this.context.find('#code-selector').val(fname);
   272      this.context.find('#prev-comment').toggleClass(
   273          'disabled', !target.prev().length);
   274      this.context.find('#next-comment').toggleClass(
   275          'disabled', !target.next().length);
   276    }
   277  
   278    // Force original file even if user hasn't changed comments since they may
   279    // have nagivated away from it within the iframe without us knowing.
   280    this.navigateToCode(currentFile);
   281  };
   282  
   283  /**
   284   * Updates the viewer by changing the height of the comments and code so that
   285   * they fit within the height of the window.  The function is typically called
   286   * after the user changes the window size.
   287   */
   288  CodewalkViewer.prototype.updateHeight = function() {
   289    var windowHeight = jQuery(window).height() - 5  // GOK
   290    var areaHeight = windowHeight - this.codeArea.offset().top
   291    var footerHeight = this.context.find('#footer').outerHeight(true)
   292    this.commentArea.height(areaHeight - footerHeight - this.context.find('#comment-options').outerHeight(true))
   293    var codeHeight = areaHeight - footerHeight - 15  // GOK
   294    this.codeArea.height(codeHeight)
   295    this.codeDisplay.height(codeHeight - this.codeDisplay.offset().top + this.codeArea.offset().top);
   296    this.sizer.height(codeHeight);
   297  };
   298  
   299  window.initFuncs.push(function() {
   300    var viewer = new CodewalkViewer(jQuery('#codewalk-main'));
   301    viewer.selectFirstComment();
   302    viewer.targetCommentLinksAtBlank();
   303    viewer.installEventHandlers();
   304    viewer.updateHeight();
   305  });