diff options
Diffstat (limited to 'doc/codewalk/codewalk.js')
| -rw-r--r-- | doc/codewalk/codewalk.js | 305 | 
1 files changed, 305 insertions, 0 deletions
| diff --git a/doc/codewalk/codewalk.js b/doc/codewalk/codewalk.js new file mode 100644 index 000000000..f780bc7a5 --- /dev/null +++ b/doc/codewalk/codewalk.js @@ -0,0 +1,305 @@ +// Copyright 2010 The Go Authors.  All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/** + * A class to hold information about the Codewalk Viewer. + * @param {jQuery} context The top element in whose context the viewer should + *     operate.  It will not touch any elements above this one. + * @constructor + */ + var CodewalkViewer = function(context) { +  this.context = context; + +  /** +   * The div that contains all of the comments and their controls. +   */ +  this.commentColumn = this.context.find('#comment-column'); + +  /** +   * The div that contains the comments proper. +   */ +  this.commentArea = this.context.find('#comment-area'); + +  /** +   * The div that wraps the iframe with the code, as well as the drop down menu +   * listing the different files. +   * @type {jQuery} +   */ +  this.codeColumn = this.context.find('#code-column'); + +  /** +   * The div that contains the code but excludes the options strip. +   * @type {jQuery} +   */ +  this.codeArea = this.context.find('#code-area'); + +  /** +   * The iframe that holds the code (from Sourcerer). +   * @type {jQuery} +   */ +  this.codeDisplay = this.context.find('#code-display'); + +  /** +   * The overlaid div used as a grab handle for sizing the code/comment panes. +   * @type {jQuery} +   */ +  this.sizer = this.context.find('#sizer'); + +  /** +   * The full-screen overlay that ensures we don't lose track of the mouse +   * while dragging. +   * @type {jQuery} +   */ +  this.overlay = this.context.find('#overlay'); + +  /** +   * The hidden input field that we use to hold the focus so that we can detect +   * shortcut keypresses. +   * @type {jQuery} +   */ +  this.shortcutInput = this.context.find('#shortcut-input'); + +  /** +   * The last comment that was selected. +   * @type {jQuery} +   */ +  this.lastSelected = null; +}; + +/** + * Minimum width of the comments or code pane, in pixels. + * @type {number} + */ +CodewalkViewer.MIN_PANE_WIDTH = 200; + +/** + * Navigate the code iframe to the given url and update the code popout link. + * @param {string} url The target URL. + * @param {Object} opt_window Window dependency injection for testing only. + */ +CodewalkViewer.prototype.navigateToCode = function(url, opt_window) { +  if (!opt_window) opt_window = window; +  // Each iframe is represented by two distinct objects in the DOM:  an iframe +  // object and a window object.  These do not expose the same capabilities. +  // Here we need to get the window representation to get the location member, +  // so we access it directly through window[] since jQuery returns the iframe +  // representation. +  // We replace location rather than set so as not to create a history for code +  // navigation. +  opt_window['code-display'].location.replace(url); +  var k = url.indexOf('&'); +  if (k != -1) url = url.slice(0, k); +  k = url.indexOf('fileprint='); +  if (k != -1) url = url.slice(k+10, url.length); +  this.context.find('#code-popout-link').attr('href', url); +}; + +/** + * Selects the first comment from the list and forces a refresh of the code + * view. + */ +CodewalkViewer.prototype.selectFirstComment = function() { +  // TODO(rsc): handle case where there are no comments +  var firstSourcererLink = this.context.find('.comment:first'); +  this.changeSelectedComment(firstSourcererLink); +}; + +/** + * Sets the target on all links nested inside comments to be _blank. + */ +CodewalkViewer.prototype.targetCommentLinksAtBlank = function() { +  this.context.find('.comment a[href], #description a[href]').each(function() { +    if (!this.target) this.target = '_blank'; +  }); +}; + +/** + * Installs event handlers for all the events we care about. + */ +CodewalkViewer.prototype.installEventHandlers = function() { +  var self = this; + +  this.context.find('.comment') +      .click(function(event) { +        if (jQuery(event.target).is('a[href]')) return true; +        self.changeSelectedComment(jQuery(this)); +        return false; +      }); + +  this.context.find('#code-selector') +      .change(function() {self.navigateToCode(jQuery(this).val());}); + +  this.context.find('#description-table .quote-feet.setting') +      .click(function() {self.toggleDescription(jQuery(this)); return false;}); + +  this.sizer +      .mousedown(function(ev) {self.startSizerDrag(ev); return false;}); +  this.overlay +      .mouseup(function(ev) {self.endSizerDrag(ev); return false;}) +      .mousemove(function(ev) {self.handleSizerDrag(ev); return false;}); + +  this.context.find('#prev-comment') +      .click(function() { +          self.changeSelectedComment(self.lastSelected.prev()); return false; +      }); + +  this.context.find('#next-comment') +      .click(function() { +          self.changeSelectedComment(self.lastSelected.next()); return false; +      }); + +  // Workaround for Firefox 2 and 3, which steal focus from the main document +  // whenever the iframe content is (re)loaded.  The input field is not shown, +  // but is a way for us to bring focus back to a place where we can detect +  // keypresses. +  this.context.find('#code-display') +      .load(function(ev) {self.shortcutInput.focus();}); + +  jQuery(document).keypress(function(ev) { +    switch(ev.which) { +      case 110:  // 'n' +          self.changeSelectedComment(self.lastSelected.next()); +          return false; +      case 112:  // 'p' +          self.changeSelectedComment(self.lastSelected.prev()); +          return false; +      default:  // ignore +    } +  }); + +  window.onresize = function() {self.updateHeight();}; +}; + +/** + * Starts dragging the pane sizer. + * @param {Object} ev The mousedown event that started us dragging. + */ +CodewalkViewer.prototype.startSizerDrag = function(ev) { +  this.initialCodeWidth = this.codeColumn.width(); +  this.initialCommentsWidth = this.commentColumn.width(); +  this.initialMouseX = ev.pageX; +  this.overlay.show(); +}; + +/** + * Handles dragging the pane sizer. + * @param {Object} ev The mousemove event updating dragging position. + */ +CodewalkViewer.prototype.handleSizerDrag = function(ev) { +  var delta = ev.pageX - this.initialMouseX; +  if (this.codeColumn.is('.right')) delta = -delta; +  var proposedCodeWidth = this.initialCodeWidth + delta; +  var proposedCommentWidth = this.initialCommentsWidth - delta; +  var mw = CodewalkViewer.MIN_PANE_WIDTH; +  if (proposedCodeWidth < mw) delta = mw - this.initialCodeWidth; +  if (proposedCommentWidth < mw) delta = this.initialCommentsWidth - mw; +  proposedCodeWidth = this.initialCodeWidth + delta; +  proposedCommentWidth = this.initialCommentsWidth - delta; +  // If window is too small, don't even try to resize. +  if (proposedCodeWidth < mw || proposedCommentWidth < mw) return; +  this.codeColumn.width(proposedCodeWidth); +  this.commentColumn.width(proposedCommentWidth); +  this.options.codeWidth = parseInt( +      this.codeColumn.width() / +      (this.codeColumn.width() + this.commentColumn.width()) * 100); +  this.context.find('#code-column-width').text(this.options.codeWidth + '%'); +}; + +/** + * Ends dragging the pane sizer. + * @param {Object} ev The mouseup event that caused us to stop dragging. + */ +CodewalkViewer.prototype.endSizerDrag = function(ev) { +  this.overlay.hide(); +  this.updateHeight(); +}; + +/** + * Toggles the Codewalk description between being shown and hidden. + * @param {jQuery} target The target that was clicked to trigger this function. + */ +CodewalkViewer.prototype.toggleDescription = function(target) { +  var description = this.context.find('#description'); +  description.toggle(); +  target.find('span').text(description.is(':hidden') ? 'show' : 'hide'); +  this.updateHeight(); +}; + +/** + * Changes the side of the window on which the code is shown and saves the + * setting in a cookie. + * @param {string?} codeSide The side on which the code should be, either + *     'left' or 'right'. + */ +CodewalkViewer.prototype.changeCodeSide = function(codeSide) { +  var commentSide = codeSide == 'left' ? 'right' : 'left'; +  this.context.find('#set-code-' + codeSide).addClass('selected'); +  this.context.find('#set-code-' + commentSide).removeClass('selected'); +  // Remove previous side class and add new one. +  this.codeColumn.addClass(codeSide).removeClass(commentSide); +  this.commentColumn.addClass(commentSide).removeClass(codeSide); +  this.sizer.css(codeSide, 'auto').css(commentSide, 0); +  this.options.codeSide = codeSide; +}; + +/** + * Adds selected class to newly selected comment, removes selected style from + * previously selected comment, changes drop down options so that the correct + * file is selected, and updates the code popout link. + * @param {jQuery} target The target that was clicked to trigger this function. + */ +CodewalkViewer.prototype.changeSelectedComment = function(target) { +  var currentFile = target.find('.comment-link').attr('href'); +  if (!currentFile) return; + +  if (!(this.lastSelected && this.lastSelected.get(0) === target.get(0))) { +    if (this.lastSelected) this.lastSelected.removeClass('selected'); +    target.addClass('selected'); +    this.lastSelected = target; +    var targetTop = target.position().top; +    var parentTop = target.parent().position().top; +    if (targetTop + target.height() > parentTop + target.parent().height() || +        targetTop < parentTop) { +      var delta = targetTop - parentTop; +      target.parent().animate( +          {'scrollTop': target.parent().scrollTop() + delta}, +          Math.max(delta / 2, 200), 'swing'); +    } +    var fname = currentFile.match(/(?:select=|fileprint=)\/[^&]+/)[0]; +    fname = fname.slice(fname.indexOf('=')+2, fname.length); +    this.context.find('#code-selector').val(fname); +    this.context.find('#prev-comment').toggleClass( +        'disabled', !target.prev().length); +    this.context.find('#next-comment').toggleClass( +        'disabled', !target.next().length); +  } + +  // Force original file even if user hasn't changed comments since they may +  // have nagivated away from it within the iframe without us knowing. +  this.navigateToCode(currentFile); +}; + +/** + * Updates the viewer by changing the height of the comments and code so that + * they fit within the height of the window.  The function is typically called + * after the user changes the window size. + */ +CodewalkViewer.prototype.updateHeight = function() { +  var windowHeight = jQuery(window).height() - 5  // GOK +  var areaHeight = windowHeight - this.codeArea.offset().top +  var footerHeight = this.context.find('#footer').outerHeight(true) +  this.commentArea.height(areaHeight - footerHeight - this.context.find('#comment-options').outerHeight(true)) +  var codeHeight = areaHeight - footerHeight - 15  // GOK +  this.codeArea.height(codeHeight) +  this.codeDisplay.height(codeHeight - this.codeDisplay.offset().top + this.codeArea.offset().top); +  this.sizer.height(codeHeight); +}; + +jQuery(document).ready(function() { +  var viewer = new CodewalkViewer(jQuery()); +  viewer.selectFirstComment(); +  viewer.targetCommentLinksAtBlank(); +  viewer.installEventHandlers(); +  viewer.updateHeight(); +}); | 
