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