From 1a2bacfb4b4b8f4d79df0335b4daf1d2cfa16d88 Mon Sep 17 00:00:00 2001 From: randx Date: Tue, 10 Jul 2012 22:52:38 +0300 Subject: [PATCH] Feature: ajax load for tree commit log --- app/assets/javascripts/application.js | 1 + app/controllers/refs_controller.rb | 23 ++- app/views/refs/_tree.html.haml | 13 +- app/views/refs/_tree_commit.html.haml | 3 + app/views/refs/_tree_item.html.haml | 18 +-- app/views/refs/logs_tree.js.haml | 9 ++ app/views/refs/tree.js.haml | 5 + config/routes.rb | 10 ++ .../javascripts/jquery.waitforimages.js | 144 ++++++++++++++++++ 9 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 app/views/refs/_tree_commit.html.haml create mode 100644 app/views/refs/logs_tree.js.haml create mode 100644 vendor/assets/javascripts/jquery.waitforimages.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 683d8fa80b4..4815d180dfb 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,6 +12,7 @@ //= require jquery.cookie //= require jquery.endless-scroll //= require jquery.highlight +//= require jquery.waitforimages //= require bootstrap-modal //= require modernizr //= require chosen-jquery diff --git a/app/controllers/refs_controller.rb b/app/controllers/refs_controller.rb index ddf5f675d0c..b610c9f34d4 100644 --- a/app/controllers/refs_controller.rb +++ b/app/controllers/refs_controller.rb @@ -9,7 +9,7 @@ class RefsController < ApplicationController before_filter :require_non_empty_project before_filter :ref - before_filter :define_tree_vars, :only => [:tree, :blob, :blame] + before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree] before_filter :render_full_content layout "project" @@ -46,6 +46,18 @@ class RefsController < ApplicationController end end + def logs_tree + contents = @tree.contents + @logs = contents.map do |content| + file = params[:path] ? File.join(params[:path], content.name) : content.name + last_commit = @project.commits(@commit.id, file, 1).last + { + :file_name => content.name, + :commit => last_commit + } + end + end + def blob if @tree.is_blob? if @tree.text? @@ -79,6 +91,15 @@ class RefsController < ApplicationController @commit = project.commit(@ref) @tree = Tree.new(@commit.tree, project, @ref, params[:path]) @tree = TreeDecorator.new(@tree) + @hex_path = Digest::SHA1.hexdigest(params[:path] || "/") + + if params[:path] + @history_path = tree_file_project_ref_path(@project, @ref, params[:path]) + @logs_path = logs_file_project_ref_path(@project, @ref, params[:path]) + else + @history_path = tree_project_ref_path(@project, @ref) + @logs_path = logs_tree_project_ref_path(@project, @ref) + end rescue return render_404 end diff --git a/app/views/refs/_tree.html.haml b/app/views/refs/_tree.html.haml index 4450afdfa3e..1965fe45b36 100644 --- a/app/views/refs/_tree.html.haml +++ b/app/views/refs/_tree.html.haml @@ -13,7 +13,7 @@ = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } - else - contents = tree.contents - %table#tree-slider.bordered-table.table + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" } %thead %th Name %th Last Update @@ -48,17 +48,18 @@ - else = simple_format(content.data) -- if params[:path] - - history_path = tree_file_project_ref_path(@project, @ref, params[:path]) -- else - - history_path = tree_project_ref_path(@project, @ref) :javascript $(function(){ $('select#branch').selectmenu({style:'popup', width:200}); $('select#tag').selectmenu({style:'popup', width:200}); $('.project-refs-select').chosen(); - history.pushState({ path: this.path }, '', "#{history_path}") + history.pushState({ path: this.path }, '', "#{@history_path}"); + + }); + + $(window).load(function(){ + $.ajax({type: "GET", url: '#{@logs_path}', dataType: "script"}); }); diff --git a/app/views/refs/_tree_commit.html.haml b/app/views/refs/_tree_commit.html.haml new file mode 100644 index 00000000000..1f2524a4c3a --- /dev/null +++ b/app/views/refs/_tree_commit.html.haml @@ -0,0 +1,3 @@ +- if tm + %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) += link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" diff --git a/app/views/refs/_tree_item.html.haml b/app/views/refs/_tree_item.html.haml index 45ea53718c0..30331944a7f 100644 --- a/app/views/refs/_tree_item.html.haml +++ b/app/views/refs/_tree_item.html.haml @@ -1,23 +1,15 @@ - file = params[:path] ? File.join(params[:path], content.name) : content.name -- content_commit = @project.commits(@commit.id, file, 1).last -- return unless content_commit -%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) } +%tr{ :class => "tree-item file_#{Digest::SHA1.hexdigest(content.name)}", :url => tree_file_project_ref_path(@project, @ref, file) } %td.tree-item-file-name - if content.is_a?(Grit::Blob) - if content.text? - = image_tag "file_txt.png" + = image_tag "file_txt.png", :class => "tree-ico" - elsif content.image? - = image_tag "file_img.png" + = image_tag "file_img.png", :class => "tree-ico" - else - = image_tag "file_bin.png" + = image_tag "file_bin.png", :class => "tree-ico" - else - = image_tag "file_dir.png" + = image_tag "file_dir.png", :class => "tree-ico" = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true %td.tree_time_ago.cgray - = time_ago_in_words(content_commit.committed_date) - ago %td.tree_commit - - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) - - if tm - %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) - = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" diff --git a/app/views/refs/logs_tree.js.haml b/app/views/refs/logs_tree.js.haml new file mode 100644 index 00000000000..ea8337398a4 --- /dev/null +++ b/app/views/refs/logs_tree.js.haml @@ -0,0 +1,9 @@ +- @logs.each do |content_data| + - file_name = content_data[:file_name] + - content_commit = content_data[:commit] + - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) + + :plain + var row = $("table.table_#{@hex_path} tr.file_#{Digest::SHA1.hexdigest(file_name)}"); + row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago'); + row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}'); diff --git a/app/views/refs/tree.js.haml b/app/views/refs/tree.js.haml index f4f28adc369..600ba62606e 100644 --- a/app/views/refs/tree.js.haml +++ b/app/views/refs/tree.js.haml @@ -2,3 +2,8 @@ $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); $("#tree-content-holder").show("slide", { direction: "right" }, 150); $('.project-refs-form #path').val("#{params[:path]}"); + + + $('#tree-slider').waitForImages(function() { + $.ajax({type: "GET", url: '#{@logs_path}', dataType: "script"}); + }); diff --git a/config/routes.rb b/config/routes.rb index 3b33ed8dfd1..73b9f643ad5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,6 +117,8 @@ Gitlab::Application.routes.draw do member do get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } + get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } + get "blob", :constraints => { :id => /[a-zA-Z.0-9\/_\-]+/, @@ -132,6 +134,14 @@ Gitlab::Application.routes.draw do :path => /.*/ } + # tree viewer + get "logs_tree/:path" => "refs#logs_tree", + :as => :logs_file, + :constraints => { + :id => /[a-zA-Z.0-9\/_\-]+/, + :path => /.*/ + } + # blame get "blame/:path" => "refs#blame", :as => :blame_file, diff --git a/vendor/assets/javascripts/jquery.waitforimages.js b/vendor/assets/javascripts/jquery.waitforimages.js new file mode 100644 index 00000000000..95b39c2e074 --- /dev/null +++ b/vendor/assets/javascripts/jquery.waitforimages.js @@ -0,0 +1,144 @@ +/* + * waitForImages 1.4 + * ----------------- + * Provides a callback when all images have loaded in your given selector. + * http://www.alexanderdickson.com/ + * + * + * Copyright (c) 2011 Alex Dickson + * Licensed under the MIT licenses. + * See website for more info. + * + */ + +;(function($) { + // Namespace all events. + var eventNamespace = 'waitForImages'; + + // CSS properties which contain references to images. + $.waitForImages = { + hasImageProperties: [ + 'backgroundImage', + 'listStyleImage', + 'borderImage', + 'borderCornerImage' + ] + }; + + // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. + $.expr[':'].uncached = function(obj) { + // Ensure we are dealing with an `img` element with a valid `src` attribute. + if ( ! $(obj).is('img[src!=""]')) { + return false; + } + + // Firefox's `complete` property will always be`true` even if the image has not been downloaded. + // Doing it this way works in Firefox. + var img = document.createElement('img'); + img.src = obj.src; + return ! img.complete; + }; + + $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) { + + // Handle options object. + if ($.isPlainObject(arguments[0])) { + eachCallback = finishedCallback.each; + waitForAll = finishedCallback.waitForAll; + finishedCallback = finishedCallback.finished; + } + + // Handle missing callbacks. + finishedCallback = finishedCallback || $.noop; + eachCallback = eachCallback || $.noop; + + // Convert waitForAll to Boolean + waitForAll = !! waitForAll; + + // Ensure callbacks are functions. + if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { + throw new TypeError('An invalid callback was supplied.'); + }; + + return this.each(function() { + // Build a list of all imgs, dependent on what images will be considered. + var obj = $(this), + allImgs = []; + + if (waitForAll) { + // CSS properties which may contain an image. + var hasImgProperties = $.waitForImages.hasImageProperties || [], + matchUrl = /url\((['"]?)(.*?)\1\)/g; + + // Get all elements, as any one of them could have a background image. + obj.find('*').each(function() { + var element = $(this); + + // If an `img` element, add it. But keep iterating in case it has a background image too. + if (element.is('img:uncached')) { + allImgs.push({ + src: element.attr('src'), + element: element[0] + }); + } + + $.each(hasImgProperties, function(i, property) { + var propertyValue = element.css(property); + // If it doesn't contain this property, skip. + if ( ! propertyValue) { + return true; + } + + // Get all url() of this element. + var match; + while (match = matchUrl.exec(propertyValue)) { + allImgs.push({ + src: match[2], + element: element[0] + }); + }; + }); + }); + } else { + // For images only, the task is simpler. + obj + .find('img:uncached') + .each(function() { + allImgs.push({ + src: this.src, + element: this + }); + }); + }; + + var allImgsLength = allImgs.length, + allImgsLoaded = 0; + + // If no images found, don't bother. + if (allImgsLength == 0) { + finishedCallback.call(obj[0]); + }; + + $.each(allImgs, function(i, img) { + + var image = new Image; + + // Handle the image loading and error with the same callback. + $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) { + allImgsLoaded++; + + // If an error occurred with loading the image, set the third argument accordingly. + eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); + + if (allImgsLoaded == allImgsLength) { + finishedCallback.call(obj[0]); + return false; + }; + + }); + + image.src = img.src; + }); + }); + }; +})(jQuery);