summaryrefslogtreecommitdiffstats
path: root/public/javascripts/diff_browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/javascripts/diff_browser.js')
-rw-r--r--public/javascripts/diff_browser.js455
1 files changed, 455 insertions, 0 deletions
diff --git a/public/javascripts/diff_browser.js b/public/javascripts/diff_browser.js
new file mode 100644
index 0000000..06c7007
--- /dev/null
+++ b/public/javascripts/diff_browser.js
@@ -0,0 +1,455 @@
+/*
+#--
+# Copyright (C) 2007-2009 Johan Sørensen <johan@johansorensen.com>
+# Copyright (C) 2009 Marius Mathiesen <marius.mathiesen@gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#--
+*/
+
+if (!Gitorious)
+ var Gitorious = {};
+
+Gitorious.Sha = function(sha) {
+ this.fullSha = sha;
+
+ this.shortSha = function() {
+ return this.fullSha.substring(0, 7);
+ };
+
+ this.sha = function() {
+ return this.fullSha;
+ }
+}
+
+Gitorious.ShaSpec = function() {
+ this.allShas = [];
+
+ this.addSha = function(s) {
+ this.allShas.push(new Gitorious.Sha(s));
+ }
+ // Add shas from a string, eg ff0-bba
+ this.parseShas = function(shaString) {
+ pair = shaString.split("-");
+ this.addSha(pair[0]);
+ if (pair.length > 1) {
+ this.addSha(pair[1]);
+ } else {
+ this.addSha(pair[0]);
+ }
+ }
+
+ this.firstSha = function() {
+ return this.allShas[0];
+ }
+
+ this.lastSha = function() {
+ return this.allShas[this.allShas.length - 1];
+ }
+
+ this.shaSpecs = function(callback) {
+ if (this.allShas.length < 2) {
+ return [this.firstSha()];
+ } else {
+ return [this.firstSha(), this.lastSha()];
+ }
+ }
+
+ this.shaSpec = function() {
+ var _specs = this.shaSpecs();
+ return jQuery.map(_specs, function(s){return s.sha()}).join("-");
+ }
+
+ this.shaSpecWithVersion = function() {
+ result = this.shaSpec();
+ if (this.hasVersion()) {
+ result = result + "@" + this.getVersion();
+ }
+ return result;
+ }
+
+ this.shortShaSpec = function() {
+ var _specs = this.shaSpecs();
+ return jQuery.map(_specs, function(s){ return s.shortSha() }).join("-");
+ }
+
+ this.singleCommit = function() {
+ return this.firstSha().sha() == this.lastSha().sha();
+ }
+
+ this.setVersion = function(v) {
+ this._version = v;
+ }
+
+ this.getVersion = function() {
+ return this._version;
+ }
+
+ this.hasVersion = function() {
+ return typeof(this._version) != "undefined";
+ }
+
+ this.summarizeHtml = function() {
+ if (this.singleCommit()) {
+ $("#current_shas .several_shas").hide();
+ $("#current_shas .single_sha").show();
+ $("#current_shas .single_sha .merge_base").html(this.firstSha().shortSha());
+ } else {
+ $("#current_shas").attr("data-merge-request-current-shas", this.shaSpec());
+ $("#current_shas .several_shas").show();
+ $("#current_shas .single_sha").hide();
+ $("#current_shas .several_shas .first").html(this.firstSha().shortSha());
+ $("#current_shas .several_shas .last").html(this.lastSha().shortSha());
+ }
+ }
+
+};
+
+Gitorious.ShaSpec.parseLocationHash = function(hash) {
+ if (hash == "" || typeof(hash) == "undefined") {
+ return null;
+ }
+ result = new Gitorious.ShaSpec();
+ _hash = hash.replace(/#/, "");
+ specAndVersion = _hash.split("@");
+ result.parseShas(specAndVersion[0]);
+ result.setVersion(specAndVersion[1]);
+ return result;
+}
+
+Gitorious.setDiffBrowserHunkStateFromCookie = function() {
+ if ($.cookie("merge-requests-diff-hunks-state") === "expanded") {
+ $('#merge_request_diff .file-diff .header').removeClass("closed").addClass("open");
+ $('#merge_request_diff .diff-hunks:hidden').show();
+ } else if ($.cookie("commits-diff-hunks-state")) {
+ if ($.cookie("commits-diff-hunks-state") === "expanded") {
+ $('#commit-diff-container .file-diff .header').removeClass("closed").addClass("open");
+ $('#commit-diff-container .diff-hunks:hidden').show();
+ } else {
+ $('#commit-diff-container .file-diff .header').removeClass("open").addClass("closed");
+ $('#commit-diff-container .diff-hunks:hidden').hide();
+ }
+ }
+}
+
+Gitorious.DiffBrowser = function(shas)
+{
+ Gitorious.disableCommenting();
+ jQuery("#merge_request_diff").html(Gitorious.MergeRequestDiffSpinner);
+ var mr_diff_url = jQuery("#merge_request_commit_selector")
+ .attr("data-merge-request-version-url");
+ jQuery.ajax({
+ "url": mr_diff_url,
+ "data": {"commit_shas": shas},
+ "success": function(data, responseText) {
+ if (responseText === "success") {
+ jQuery("#merge_request_diff").html(data);
+ var commentMarkup = jQuery("#__temp_comments").html();
+ jQuery("#__temp_comments").html("");
+ jQuery("#merge_request_comments").html(commentMarkup);
+ var shaSpec = new Gitorious.ShaSpec();
+ shaSpec.parseShas(shas);
+ Gitorious.MergeRequestController.getInstance().didReceiveVersion(shaSpec);
+ Gitorious.setDiffBrowserHunkStateFromCookie();
+ Gitorious.enableCommenting();
+ Gitorious.DiffBrowser.KeyNavigation.enable();
+ }
+ },
+ "error": function(xhr, statusText, errorThrown) {
+ jQuery("#merge_request_diff").html("<div class=\"merge_request_diff_loading_indicator\">" +
+ "An error has occured. Please try again later.</div>");
+ }
+ });
+}
+
+Gitorious.DiffBrowser.CommentHighlighter = {
+ _lastHighlightedComment: null,
+
+ removePrevious: function() {
+ var self = Gitorious.DiffBrowser.CommentHighlighter
+ if (!self._lastHighlightedComment)
+ return;
+ self.remove(self._lastHighlightedComment);
+ },
+
+ add: function(commentElement) {
+ Gitorious.DiffBrowser.CommentHighlighter.removePrevious();
+ commentElement.addClass("highlighted");
+ $.each(commentElement.attr("gts:lines").split(","), function() {
+ commentElement.parents("table").find("tr.line-" + this).addClass("highlighted");
+ });
+ Gitorious.DiffBrowser.CommentHighlighter._lastHighlightedComment = commentElement;
+ },
+
+ remove: function(commentElement) {
+ commentElement.removeClass("highlighted");
+ $.each(commentElement.attr("gts:lines").split(","), function() {
+ commentElement.parents("table").find("tr.line-" + this).removeClass("highlighted");
+ });
+ }
+};
+
+Gitorious.DiffBrowser.KeyNavigation = {
+ _currentIndex: 0,
+
+ _callback: function(event) {
+ var scrollToCommentAtCurrentIndex = function(commentElement) {
+ var elements = $("table tr td .diff-comments .diff-comment");
+ if (Gitorious.DiffBrowser.KeyNavigation._currentIndex >= elements.length ||
+ Gitorious.DiffBrowser.KeyNavigation._currentIndex <= 0)
+ {
+ Gitorious.DiffBrowser.KeyNavigation._currentIndex = 0;
+ }
+ var element = $(elements[Gitorious.DiffBrowser.KeyNavigation._currentIndex]);
+ element.parents(".diff-comments:hidden").slideDown();
+ window.scrollTo(0, element.position().top + window.innerHeight);
+ Gitorious.DiffBrowser.CommentHighlighter.add(element);
+ };
+
+ if (event.keyCode === 74) { // j
+ scrollToCommentAtCurrentIndex();
+ Gitorious.DiffBrowser.KeyNavigation._currentIndex++;
+ } else if (event.keyCode === 75) { // k
+ Gitorious.DiffBrowser.KeyNavigation._currentIndex--;
+ scrollToCommentAtCurrentIndex();
+ }
+ },
+
+ enable: function() {
+ Gitorious.DiffBrowser.KeyNavigation.disable()
+ $(window).keydown(Gitorious.DiffBrowser.KeyNavigation._callback);
+ // unbind whenever we're in an input field
+ $(":input").focus(function() {
+ Gitorious.DiffBrowser.KeyNavigation.disable();
+ });
+ $(":input").blur(function() {
+ $(window).keydown(Gitorious.DiffBrowser.KeyNavigation._callback);
+ });
+ },
+
+ disable: function() {
+ $(window).unbind("keydown", Gitorious.DiffBrowser.KeyNavigation._callback);
+ }
+};
+
+Gitorious.MergeRequestController = function() {
+ this.willSelectShas = function() {
+ $("#current_shas .label").html("Selecting");
+ }
+
+ this.didReceiveVersion = function(spec) {
+ spec.setVersion(this.determineCurrentVersion());
+ document.location.hash = spec.shaSpecWithVersion();
+ }
+
+ this.determineCurrentVersion = function() {
+ return $("#merge_request_version").text().replace(/[^0-9]+/g,'');
+ }
+
+ this.isSelectingShas = function(spec) {
+ spec.setVersion(this.determineCurrentVersion());
+ document.location.hash = spec.shaSpecWithVersion();
+ spec.summarizeHtml();
+ }
+
+ this.findCurrentlySelectedShas = function(spec) {
+ var allShas = jQuery("li.single_commit a").map(function(){
+ return $(this).attr("data-commit-sha");
+ })
+ var currentShas = [];
+ for (var i = allShas.indexOf(spec.firstSha().sha());
+ i <= allShas.indexOf(spec.lastSha().sha());
+ i++) {
+ currentShas.push(allShas[i]);
+ }
+ return currentShas;
+ }
+
+ // Loads the requested (from path part of uri) shas and version
+ this.loadFromBookmark = function(spec) {
+ jQuery("li.ui-selected").removeClass("ui-selected");
+ var currentShas = this.findCurrentlySelectedShas(spec);
+ jQuery.each(currentShas, function(ind, sha){
+ jQuery("[data-commit-sha='" + sha + "']").parent().addClass("ui-selected");
+ })
+ }
+
+ this.didSelectShas = function(spec) {
+ $("#current_shas .label").html("Showing");
+
+ // In case a range has been selected, also display what's in between as selected
+ var currentShas = this.findCurrentlySelectedShas(spec);
+ jQuery.each(currentShas, function(idx,sha){
+ var l = jQuery("[data-commit-sha='" + sha + "']").parent();
+ if (!l.hasClass("ui-selected")) {
+ l.addClass("ui-selected");
+ }
+ });
+
+ var mr_diff_url = jQuery("#merge_request_commit_selector")
+ .attr("data-merge-request-version-url");
+ var diff_browser = new Gitorious.DiffBrowser(spec.shaSpec());
+ }
+}
+
+Gitorious.MergeRequestController.getInstance = function() {
+ if (Gitorious.MergeRequestController._instance) {
+ return Gitorious.MergeRequestController._instance;
+ } else {
+ var result = new Gitorious.MergeRequestController();
+ Gitorious.MergeRequestController._instance = result;
+ return result;
+ }
+}
+
+// To preserve memory and avoid errors, we remove the selectables
+Gitorious.disableCommenting = function() {
+ jQuery("table.codediff").selectable("destroy");
+}
+
+// Makes line numbers selectable for commenting
+Gitorious.enableCommenting = function() {
+ jQuery("table.codediff").selectable({
+ filter: "td.commentable",
+ start: function(e, ui) {
+ Gitorious.CommentForm.destroyAll();
+ },
+ cancel: ".inline_comments",
+ stop: function(e, ui) {
+ var diffTable = e.target;
+ $(diffTable).find("td.ui-selected").each(function(el){
+ $(this).parent().addClass("selected-for-commenting");
+ })
+ var allLineNumbers = $(diffTable).find("td.ui-selected").map(function(){
+ return $(this).text();
+ });
+ var path = $(diffTable).parent().prev(".header").children(".title").text();
+ var commentForm = new Gitorious.CommentForm(path);
+ commentForm.setLineNumbers(allLineNumbers);
+ var commentContainer = $(diffTable).prev(".comment_container");
+ if (commentForm.hasLines()) {
+ commentForm.display({inside: commentContainer});
+ }
+ }
+ });
+
+ // Comment highlighting of associated lines
+ $("table tr td.code .diff-comment").each(function() {
+ var lines = $(this).attr("gts:lines").split(",");
+ var replyCallback = function() {
+ Gitorious.CommentForm.destroyAll();
+ var lines = $(this).parents("div.diff-comment").attr("gts:lines").split(",")
+ var path = $(this).parents("table").parent().prev(".header"
+ ).children(".title").text();
+ var commentForm = new Gitorious.CommentForm(path);
+ commentForm.setLineNumbers(lines);
+ if (commentForm.hasLines())
+ commentForm.display({
+ inside: $(this).parents("table").prev(".comment_container"),
+ trigger: $(this)
+ });
+ return false;
+ };
+ $(this).hover(function() {
+ Gitorious.DiffBrowser.CommentHighlighter.add($(this));
+ $(this).find(".reply").show().click(replyCallback);
+ }, function() {
+ Gitorious.DiffBrowser.CommentHighlighter.remove($(this))
+ $(this).find(".reply").hide().unbind("click", replyCallback);
+ });
+ });
+
+}
+
+Gitorious.CommentForm = function(path){
+ this.path = path;
+ this.numbers = [];
+ this.setLineNumbers = function(n) {
+ result = [];
+ n.each(function(i,number){
+ if (number != "") {
+ result.push(number);
+ }
+ });
+ this.numbers = result;
+ }
+ this.linesAsString = function() {
+ var sortedLines = this.numbers.sort();
+ return sortedLines[0] + ".." + sortedLines[sortedLines.length - 1];
+ }
+ this.hasLines = function() {
+ return this.numbers.length > 0;
+ }
+ this.getSummary = function() {
+ return "Commenting on lines " + this.linesAsString() + " in " + this.path;
+ }
+ this.display = function(options) {
+ Gitorious.DiffBrowser.KeyNavigation.disable();
+ var comment_form = jQuery("#inline_comment_form");
+ var hash = document.location.hash;
+ var commentContainer = options.inside;
+ commentContainer.html(comment_form.html());
+ commentContainer.find("#description").text(this.getSummary());
+ var shas = hash.split("@")[0].replace("#","");
+ commentContainer.find("#comment_sha1").val(shas);
+ commentContainer.find("#comment_path").val(this.path);
+ commentContainer.find(".cancel_button").click(Gitorious.CommentForm.destroyAll);
+ commentContainer.find("#comment_lines").val(this.linesAsString());
+ this._positionAndShowContainer(commentContainer, options.trigger);
+ commentContainer.find("#comment_body").focus();
+ var zeForm = commentContainer.find("form");
+ zeForm.submit(function(){
+ zeForm.find(".progress").show("fast");
+ jQuery.ajax({
+ "url": $(this).attr("action"),
+ "data": $(this).serialize(),
+ "type": "POST",
+ "success": function(data, text) {
+ Gitorious.CommentForm.destroyAll();
+ new Gitorious.DiffBrowser(shas);
+ },
+ "error": function(xhr, statusText, errorThrown) {
+ var errorDisplay = $(zeForm).find(".error");
+ zeForm.find(".progress").hide("fast");
+ errorDisplay.text("Please make sure your comment is valid");
+ errorDisplay.show("fast");
+ }
+ });
+ return false;
+ });
+
+ commentContainer.keydown(function(e){
+ if (e.which == 27) { // Escape
+ Gitorious.CommentForm.destroyAll();
+ }
+ })
+
+ },
+
+ // Positions the commentContainer, optionally near the trigger
+ this._positionAndShowContainer = function(container, trigger) {
+ var data = { left: $(document).width() - container.width() - 75 + "px" };
+ if (trigger)
+ data.top = trigger.position().top + "px";
+ container.css(data);
+ container.slideDown();
+ }
+}
+
+Gitorious.CommentForm.destroyAll = function() {
+ $(".comment_container").html("").unbind("keypress").slideUp("fast");
+ $(".selected-for-commenting").removeClass("selected-for-commenting");
+ $(".ui-selected").removeClass("ui-selected");
+ Gitorious.DiffBrowser.KeyNavigation.enable();
+}