You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
11 KiB
306 lines
11 KiB
// 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 navigated 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);
|
|
};
|
|
|
|
window.initFuncs.push(function() {
|
|
var viewer = new CodewalkViewer(jQuery('#codewalk-main'));
|
|
viewer.selectFirstComment();
|
|
viewer.targetCommentLinksAtBlank();
|
|
viewer.installEventHandlers();
|
|
viewer.updateHeight();
|
|
});
|