Commit 1a2bacfb4b4b8f4d79df0335b4daf1d2cfa16d88
1 parent
b8425cf1
Exists in
master
and in
4 other branches
Feature: ajax load for tree commit log
Showing
9 changed files
with
206 additions
and
20 deletions
Show diff stats
app/assets/javascripts/application.js
| @@ -12,6 +12,7 @@ | @@ -12,6 +12,7 @@ | ||
| 12 | //= require jquery.cookie | 12 | //= require jquery.cookie |
| 13 | //= require jquery.endless-scroll | 13 | //= require jquery.endless-scroll |
| 14 | //= require jquery.highlight | 14 | //= require jquery.highlight |
| 15 | +//= require jquery.waitforimages | ||
| 15 | //= require bootstrap-modal | 16 | //= require bootstrap-modal |
| 16 | //= require modernizr | 17 | //= require modernizr |
| 17 | //= require chosen-jquery | 18 | //= require chosen-jquery |
app/controllers/refs_controller.rb
| @@ -9,7 +9,7 @@ class RefsController < ApplicationController | @@ -9,7 +9,7 @@ class RefsController < ApplicationController | ||
| 9 | before_filter :require_non_empty_project | 9 | before_filter :require_non_empty_project |
| 10 | 10 | ||
| 11 | before_filter :ref | 11 | before_filter :ref |
| 12 | - before_filter :define_tree_vars, :only => [:tree, :blob, :blame] | 12 | + before_filter :define_tree_vars, :only => [:tree, :blob, :blame, :logs_tree] |
| 13 | before_filter :render_full_content | 13 | before_filter :render_full_content |
| 14 | 14 | ||
| 15 | layout "project" | 15 | layout "project" |
| @@ -46,6 +46,18 @@ class RefsController < ApplicationController | @@ -46,6 +46,18 @@ class RefsController < ApplicationController | ||
| 46 | end | 46 | end |
| 47 | end | 47 | end |
| 48 | 48 | ||
| 49 | + def logs_tree | ||
| 50 | + contents = @tree.contents | ||
| 51 | + @logs = contents.map do |content| | ||
| 52 | + file = params[:path] ? File.join(params[:path], content.name) : content.name | ||
| 53 | + last_commit = @project.commits(@commit.id, file, 1).last | ||
| 54 | + { | ||
| 55 | + :file_name => content.name, | ||
| 56 | + :commit => last_commit | ||
| 57 | + } | ||
| 58 | + end | ||
| 59 | + end | ||
| 60 | + | ||
| 49 | def blob | 61 | def blob |
| 50 | if @tree.is_blob? | 62 | if @tree.is_blob? |
| 51 | if @tree.text? | 63 | if @tree.text? |
| @@ -79,6 +91,15 @@ class RefsController < ApplicationController | @@ -79,6 +91,15 @@ class RefsController < ApplicationController | ||
| 79 | @commit = project.commit(@ref) | 91 | @commit = project.commit(@ref) |
| 80 | @tree = Tree.new(@commit.tree, project, @ref, params[:path]) | 92 | @tree = Tree.new(@commit.tree, project, @ref, params[:path]) |
| 81 | @tree = TreeDecorator.new(@tree) | 93 | @tree = TreeDecorator.new(@tree) |
| 94 | + @hex_path = Digest::SHA1.hexdigest(params[:path] || "/") | ||
| 95 | + | ||
| 96 | + if params[:path] | ||
| 97 | + @history_path = tree_file_project_ref_path(@project, @ref, params[:path]) | ||
| 98 | + @logs_path = logs_file_project_ref_path(@project, @ref, params[:path]) | ||
| 99 | + else | ||
| 100 | + @history_path = tree_project_ref_path(@project, @ref) | ||
| 101 | + @logs_path = logs_tree_project_ref_path(@project, @ref) | ||
| 102 | + end | ||
| 82 | rescue | 103 | rescue |
| 83 | return render_404 | 104 | return render_404 |
| 84 | end | 105 | end |
app/views/refs/_tree.html.haml
| @@ -13,7 +13,7 @@ | @@ -13,7 +13,7 @@ | ||
| 13 | = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } | 13 | = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } |
| 14 | - else | 14 | - else |
| 15 | - contents = tree.contents | 15 | - contents = tree.contents |
| 16 | - %table#tree-slider.bordered-table.table | 16 | + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" } |
| 17 | %thead | 17 | %thead |
| 18 | %th Name | 18 | %th Name |
| 19 | %th Last Update | 19 | %th Last Update |
| @@ -48,17 +48,18 @@ | @@ -48,17 +48,18 @@ | ||
| 48 | - else | 48 | - else |
| 49 | = simple_format(content.data) | 49 | = simple_format(content.data) |
| 50 | 50 | ||
| 51 | -- if params[:path] | ||
| 52 | - - history_path = tree_file_project_ref_path(@project, @ref, params[:path]) | ||
| 53 | -- else | ||
| 54 | - - history_path = tree_project_ref_path(@project, @ref) | ||
| 55 | :javascript | 51 | :javascript |
| 56 | $(function(){ | 52 | $(function(){ |
| 57 | $('select#branch').selectmenu({style:'popup', width:200}); | 53 | $('select#branch').selectmenu({style:'popup', width:200}); |
| 58 | $('select#tag').selectmenu({style:'popup', width:200}); | 54 | $('select#tag').selectmenu({style:'popup', width:200}); |
| 59 | $('.project-refs-select').chosen(); | 55 | $('.project-refs-select').chosen(); |
| 60 | 56 | ||
| 61 | - history.pushState({ path: this.path }, '', "#{history_path}") | 57 | + history.pushState({ path: this.path }, '', "#{@history_path}"); |
| 58 | + | ||
| 59 | + }); | ||
| 60 | + | ||
| 61 | + $(window).load(function(){ | ||
| 62 | + $.ajax({type: "GET", url: '#{@logs_path}', dataType: "script"}); | ||
| 62 | }); | 63 | }); |
| 63 | 64 | ||
| 64 | 65 |
app/views/refs/_tree_item.html.haml
| 1 | - file = params[:path] ? File.join(params[:path], content.name) : content.name | 1 | - file = params[:path] ? File.join(params[:path], content.name) : content.name |
| 2 | -- content_commit = @project.commits(@commit.id, file, 1).last | ||
| 3 | -- return unless content_commit | ||
| 4 | -%tr{ :class => "tree-item", :url => tree_file_project_ref_path(@project, @ref, file) } | 2 | +%tr{ :class => "tree-item file_#{Digest::SHA1.hexdigest(content.name)}", :url => tree_file_project_ref_path(@project, @ref, file) } |
| 5 | %td.tree-item-file-name | 3 | %td.tree-item-file-name |
| 6 | - if content.is_a?(Grit::Blob) | 4 | - if content.is_a?(Grit::Blob) |
| 7 | - if content.text? | 5 | - if content.text? |
| 8 | - = image_tag "file_txt.png" | 6 | + = image_tag "file_txt.png", :class => "tree-ico" |
| 9 | - elsif content.image? | 7 | - elsif content.image? |
| 10 | - = image_tag "file_img.png" | 8 | + = image_tag "file_img.png", :class => "tree-ico" |
| 11 | - else | 9 | - else |
| 12 | - = image_tag "file_bin.png" | 10 | + = image_tag "file_bin.png", :class => "tree-ico" |
| 13 | - else | 11 | - else |
| 14 | - = image_tag "file_dir.png" | 12 | + = image_tag "file_dir.png", :class => "tree-ico" |
| 15 | = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true | 13 | = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true |
| 16 | %td.tree_time_ago.cgray | 14 | %td.tree_time_ago.cgray |
| 17 | - = time_ago_in_words(content_commit.committed_date) | ||
| 18 | - ago | ||
| 19 | %td.tree_commit | 15 | %td.tree_commit |
| 20 | - - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) | ||
| 21 | - - if tm | ||
| 22 | - %strong= link_to "[#{tm.user_name}]", project_team_member_path(@project, tm) | ||
| 23 | - = link_to truncate(content_commit.safe_message, :length => tm ? 30 : 50), project_commit_path(@project, content_commit.id), :class => "tree-commit-link" |
| @@ -0,0 +1,9 @@ | @@ -0,0 +1,9 @@ | ||
| 1 | +- @logs.each do |content_data| | ||
| 2 | + - file_name = content_data[:file_name] | ||
| 3 | + - content_commit = content_data[:commit] | ||
| 4 | + - tm = @project.team_member_by_name_or_email(content_commit.author_email, content_commit.author_name) | ||
| 5 | + | ||
| 6 | + :plain | ||
| 7 | + var row = $("table.table_#{@hex_path} tr.file_#{Digest::SHA1.hexdigest(file_name)}"); | ||
| 8 | + row.find("td.tree_time_ago").html('#{escape_javascript(time_ago_in_words(content_commit.committed_date))} ago'); | ||
| 9 | + row.find("td.tree_commit").html('#{escape_javascript(render("tree_commit", :tm => tm, :content_commit => content_commit))}'); |
app/views/refs/tree.js.haml
| @@ -2,3 +2,8 @@ | @@ -2,3 +2,8 @@ | ||
| 2 | $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); | 2 | $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); |
| 3 | $("#tree-content-holder").show("slide", { direction: "right" }, 150); | 3 | $("#tree-content-holder").show("slide", { direction: "right" }, 150); |
| 4 | $('.project-refs-form #path').val("#{params[:path]}"); | 4 | $('.project-refs-form #path').val("#{params[:path]}"); |
| 5 | + | ||
| 6 | + | ||
| 7 | + $('#tree-slider').waitForImages(function() { | ||
| 8 | + $.ajax({type: "GET", url: '#{@logs_path}', dataType: "script"}); | ||
| 9 | + }); |
config/routes.rb
| @@ -117,6 +117,8 @@ Gitlab::Application.routes.draw do | @@ -117,6 +117,8 @@ Gitlab::Application.routes.draw do | ||
| 117 | 117 | ||
| 118 | member do | 118 | member do |
| 119 | get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } | 119 | get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } |
| 120 | + get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } | ||
| 121 | + | ||
| 120 | get "blob", | 122 | get "blob", |
| 121 | :constraints => { | 123 | :constraints => { |
| 122 | :id => /[a-zA-Z.0-9\/_\-]+/, | 124 | :id => /[a-zA-Z.0-9\/_\-]+/, |
| @@ -132,6 +134,14 @@ Gitlab::Application.routes.draw do | @@ -132,6 +134,14 @@ Gitlab::Application.routes.draw do | ||
| 132 | :path => /.*/ | 134 | :path => /.*/ |
| 133 | } | 135 | } |
| 134 | 136 | ||
| 137 | + # tree viewer | ||
| 138 | + get "logs_tree/:path" => "refs#logs_tree", | ||
| 139 | + :as => :logs_file, | ||
| 140 | + :constraints => { | ||
| 141 | + :id => /[a-zA-Z.0-9\/_\-]+/, | ||
| 142 | + :path => /.*/ | ||
| 143 | + } | ||
| 144 | + | ||
| 135 | # blame | 145 | # blame |
| 136 | get "blame/:path" => "refs#blame", | 146 | get "blame/:path" => "refs#blame", |
| 137 | :as => :blame_file, | 147 | :as => :blame_file, |
| @@ -0,0 +1,144 @@ | @@ -0,0 +1,144 @@ | ||
| 1 | +/* | ||
| 2 | + * waitForImages 1.4 | ||
| 3 | + * ----------------- | ||
| 4 | + * Provides a callback when all images have loaded in your given selector. | ||
| 5 | + * http://www.alexanderdickson.com/ | ||
| 6 | + * | ||
| 7 | + * | ||
| 8 | + * Copyright (c) 2011 Alex Dickson | ||
| 9 | + * Licensed under the MIT licenses. | ||
| 10 | + * See website for more info. | ||
| 11 | + * | ||
| 12 | + */ | ||
| 13 | + | ||
| 14 | +;(function($) { | ||
| 15 | + // Namespace all events. | ||
| 16 | + var eventNamespace = 'waitForImages'; | ||
| 17 | + | ||
| 18 | + // CSS properties which contain references to images. | ||
| 19 | + $.waitForImages = { | ||
| 20 | + hasImageProperties: [ | ||
| 21 | + 'backgroundImage', | ||
| 22 | + 'listStyleImage', | ||
| 23 | + 'borderImage', | ||
| 24 | + 'borderCornerImage' | ||
| 25 | + ] | ||
| 26 | + }; | ||
| 27 | + | ||
| 28 | + // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. | ||
| 29 | + $.expr[':'].uncached = function(obj) { | ||
| 30 | + // Ensure we are dealing with an `img` element with a valid `src` attribute. | ||
| 31 | + if ( ! $(obj).is('img[src!=""]')) { | ||
| 32 | + return false; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + // Firefox's `complete` property will always be`true` even if the image has not been downloaded. | ||
| 36 | + // Doing it this way works in Firefox. | ||
| 37 | + var img = document.createElement('img'); | ||
| 38 | + img.src = obj.src; | ||
| 39 | + return ! img.complete; | ||
| 40 | + }; | ||
| 41 | + | ||
| 42 | + $.fn.waitForImages = function(finishedCallback, eachCallback, waitForAll) { | ||
| 43 | + | ||
| 44 | + // Handle options object. | ||
| 45 | + if ($.isPlainObject(arguments[0])) { | ||
| 46 | + eachCallback = finishedCallback.each; | ||
| 47 | + waitForAll = finishedCallback.waitForAll; | ||
| 48 | + finishedCallback = finishedCallback.finished; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + // Handle missing callbacks. | ||
| 52 | + finishedCallback = finishedCallback || $.noop; | ||
| 53 | + eachCallback = eachCallback || $.noop; | ||
| 54 | + | ||
| 55 | + // Convert waitForAll to Boolean | ||
| 56 | + waitForAll = !! waitForAll; | ||
| 57 | + | ||
| 58 | + // Ensure callbacks are functions. | ||
| 59 | + if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { | ||
| 60 | + throw new TypeError('An invalid callback was supplied.'); | ||
| 61 | + }; | ||
| 62 | + | ||
| 63 | + return this.each(function() { | ||
| 64 | + // Build a list of all imgs, dependent on what images will be considered. | ||
| 65 | + var obj = $(this), | ||
| 66 | + allImgs = []; | ||
| 67 | + | ||
| 68 | + if (waitForAll) { | ||
| 69 | + // CSS properties which may contain an image. | ||
| 70 | + var hasImgProperties = $.waitForImages.hasImageProperties || [], | ||
| 71 | + matchUrl = /url\((['"]?)(.*?)\1\)/g; | ||
| 72 | + | ||
| 73 | + // Get all elements, as any one of them could have a background image. | ||
| 74 | + obj.find('*').each(function() { | ||
| 75 | + var element = $(this); | ||
| 76 | + | ||
| 77 | + // If an `img` element, add it. But keep iterating in case it has a background image too. | ||
| 78 | + if (element.is('img:uncached')) { | ||
| 79 | + allImgs.push({ | ||
| 80 | + src: element.attr('src'), | ||
| 81 | + element: element[0] | ||
| 82 | + }); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + $.each(hasImgProperties, function(i, property) { | ||
| 86 | + var propertyValue = element.css(property); | ||
| 87 | + // If it doesn't contain this property, skip. | ||
| 88 | + if ( ! propertyValue) { | ||
| 89 | + return true; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + // Get all url() of this element. | ||
| 93 | + var match; | ||
| 94 | + while (match = matchUrl.exec(propertyValue)) { | ||
| 95 | + allImgs.push({ | ||
| 96 | + src: match[2], | ||
| 97 | + element: element[0] | ||
| 98 | + }); | ||
| 99 | + }; | ||
| 100 | + }); | ||
| 101 | + }); | ||
| 102 | + } else { | ||
| 103 | + // For images only, the task is simpler. | ||
| 104 | + obj | ||
| 105 | + .find('img:uncached') | ||
| 106 | + .each(function() { | ||
| 107 | + allImgs.push({ | ||
| 108 | + src: this.src, | ||
| 109 | + element: this | ||
| 110 | + }); | ||
| 111 | + }); | ||
| 112 | + }; | ||
| 113 | + | ||
| 114 | + var allImgsLength = allImgs.length, | ||
| 115 | + allImgsLoaded = 0; | ||
| 116 | + | ||
| 117 | + // If no images found, don't bother. | ||
| 118 | + if (allImgsLength == 0) { | ||
| 119 | + finishedCallback.call(obj[0]); | ||
| 120 | + }; | ||
| 121 | + | ||
| 122 | + $.each(allImgs, function(i, img) { | ||
| 123 | + | ||
| 124 | + var image = new Image; | ||
| 125 | + | ||
| 126 | + // Handle the image loading and error with the same callback. | ||
| 127 | + $(image).bind('load.' + eventNamespace + ' error.' + eventNamespace, function(event) { | ||
| 128 | + allImgsLoaded++; | ||
| 129 | + | ||
| 130 | + // If an error occurred with loading the image, set the third argument accordingly. | ||
| 131 | + eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); | ||
| 132 | + | ||
| 133 | + if (allImgsLoaded == allImgsLength) { | ||
| 134 | + finishedCallback.call(obj[0]); | ||
| 135 | + return false; | ||
| 136 | + }; | ||
| 137 | + | ||
| 138 | + }); | ||
| 139 | + | ||
| 140 | + image.src = img.src; | ||
| 141 | + }); | ||
| 142 | + }); | ||
| 143 | + }; | ||
| 144 | +})(jQuery); |