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); |