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
app/controllers/refs_controller.rb
... | ... | @@ -9,7 +9,7 @@ class RefsController < ApplicationController |
9 | 9 | before_filter :require_non_empty_project |
10 | 10 | |
11 | 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 | 13 | before_filter :render_full_content |
14 | 14 | |
15 | 15 | layout "project" |
... | ... | @@ -46,6 +46,18 @@ class RefsController < ApplicationController |
46 | 46 | end |
47 | 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 | 61 | def blob |
50 | 62 | if @tree.is_blob? |
51 | 63 | if @tree.text? |
... | ... | @@ -79,6 +91,15 @@ class RefsController < ApplicationController |
79 | 91 | @commit = project.commit(@ref) |
80 | 92 | @tree = Tree.new(@commit.tree, project, @ref, params[:path]) |
81 | 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 | 103 | rescue |
83 | 104 | return render_404 |
84 | 105 | end | ... | ... |
app/views/refs/_tree.html.haml
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 | = render :partial => "refs/tree_file", :locals => { :name => tree.name, :content => tree.data, :file => tree } |
14 | 14 | - else |
15 | 15 | - contents = tree.contents |
16 | - %table#tree-slider.bordered-table.table | |
16 | + %table#tree-slider.bordered-table.table{:class => "table_#{@hex_path}" } | |
17 | 17 | %thead |
18 | 18 | %th Name |
19 | 19 | %th Last Update |
... | ... | @@ -48,17 +48,18 @@ |
48 | 48 | - else |
49 | 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 | 51 | :javascript |
56 | 52 | $(function(){ |
57 | 53 | $('select#branch').selectmenu({style:'popup', width:200}); |
58 | 54 | $('select#tag').selectmenu({style:'popup', width:200}); |
59 | 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 | 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 | 3 | %td.tree-item-file-name |
6 | 4 | - if content.is_a?(Grit::Blob) |
7 | 5 | - if content.text? |
8 | - = image_tag "file_txt.png" | |
6 | + = image_tag "file_txt.png", :class => "tree-ico" | |
9 | 7 | - elsif content.image? |
10 | - = image_tag "file_img.png" | |
8 | + = image_tag "file_img.png", :class => "tree-ico" | |
11 | 9 | - else |
12 | - = image_tag "file_bin.png" | |
10 | + = image_tag "file_bin.png", :class => "tree-ico" | |
13 | 11 | - else |
14 | - = image_tag "file_dir.png" | |
12 | + = image_tag "file_dir.png", :class => "tree-ico" | |
15 | 13 | = link_to truncate(content.name, :length => 40), tree_file_project_ref_path(@project, @ref || @commit.id, file), :remote => :true |
16 | 14 | %td.tree_time_ago.cgray |
17 | - = time_ago_in_words(content_commit.committed_date) | |
18 | - ago | |
19 | 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 @@ |
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 | 2 | $("#tree-holder").html("#{escape_javascript(render(:partial => "tree", :locals => {:repo => @repo, :commit => @commit, :tree => @tree}))}"); |
3 | 3 | $("#tree-content-holder").show("slide", { direction: "right" }, 150); |
4 | 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 | 117 | |
118 | 118 | member do |
119 | 119 | get "tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } |
120 | + get "logs_tree", :constraints => { :id => /[a-zA-Z.\/0-9_\-]+/ } | |
121 | + | |
120 | 122 | get "blob", |
121 | 123 | :constraints => { |
122 | 124 | :id => /[a-zA-Z.0-9\/_\-]+/, |
... | ... | @@ -132,6 +134,14 @@ Gitlab::Application.routes.draw do |
132 | 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 | 145 | # blame |
136 | 146 | get "blame/:path" => "refs#blame", |
137 | 147 | :as => :blame_file, | ... | ... |
... | ... | @@ -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); | ... | ... |