diff --git a/Gemfile b/Gemfile index c9423e923..1d2558648 100644 --- a/Gemfile +++ b/Gemfile @@ -58,6 +58,11 @@ gem 'rest-client', '~> 1.6.6' gem 'attr_encrypted', '1.2.1' gem "gemoji", "~> 1.2.1", require: 'emoji/railtie' +# AngularJS related stuff +gem 'angularjs-rails' +gem 'ng-rails-csrf' +# gem 'angularjs-rails-resource' + group :assets do gem 'sass-rails', '~> 3.2.5' gem 'coffee-rails', '~> 3.2.2' diff --git a/Gemfile.lock b/Gemfile.lock index dfe789d3c..acd63234d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,8 @@ GEM json ancestry (1.3.0) activerecord (>= 2.3.14) + angularjs-rails (1.0.7) + angularjs-rails-resource (0.2.0) arel (3.0.2) attr_encrypted (1.2.1) encryptor (>= 1.1.1) @@ -209,6 +211,7 @@ GEM net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) newrelic_rpm (3.5.5.38) + ng-rails-csrf (0.1.0) nokogiri (1.5.9) oauth2 (0.8.1) faraday (~> 0.8) @@ -407,6 +410,8 @@ DEPENDENCIES RedCloth airbrake (~> 3.1.2) ancestry (~> 1.3.0) + angularjs-rails + angularjs-rails-resource attr_encrypted (= 1.2.1) better_errors binding_of_caller @@ -437,6 +442,7 @@ DEPENDENCIES meta_request mock_redis (= 0.6.2) newrelic_rpm (~> 3.5.5.38) + ng-rails-csrf omniauth omniauth-facebook omniauth-github diff --git a/app/assets/javascripts/angularjs/config.js b/app/assets/javascripts/angularjs/config.js new file mode 100644 index 000000000..8147cb81a --- /dev/null +++ b/app/assets/javascripts/angularjs/config.js @@ -0,0 +1 @@ +var RosaABF = angular.module('RosaABF', ['ngResource', 'ng-rails-csrf']); diff --git a/app/assets/javascripts/angularjs/controllers/project_refs_controller.js b/app/assets/javascripts/angularjs/controllers/project_refs_controller.js new file mode 100644 index 000000000..56341a452 --- /dev/null +++ b/app/assets/javascripts/angularjs/controllers/project_refs_controller.js @@ -0,0 +1,44 @@ +RosaABF.controller('ProjectRefsController', function($scope, $http, $location, ApiProject) { + + $scope.branches = []; + $scope.tags = []; + $scope.project_id = null; + $scope.current_ref = null; + + $scope.init = function(project_id, ref) { + $scope.project_id = project_id; + $scope.current_ref = ref; + ApiProject.project($scope.project_id, function(result){ + $scope.project = result; + }); + $scope.getRefs(); + } + + $scope.getRefs = function() { + //returns [ProjectRef, ProjectRef, ...] + ApiProject.refs($scope.project_id, function(results){ + _.each(results, function(result){ + if (result.isTag) { + if (result.ref == $scope.current_ref) { + $scope.tags.unshift(result); + } else { + $scope.tags.push(result); + } + } else { + if (result.ref == $scope.current_ref) { + $scope.branches.unshift(result); + } else { + $scope.branches.push(result); + } + } + }); + }); + }; + + $scope.destroy = function(branch) { + var path = $location.$$absUrl.replace(/\/[\w\-]+$/, '') + '/' + branch.name; + $http.delete(path).success(function(data) { + $scope.getRefs(); + }); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/angularjs/models/project.js b/app/assets/javascripts/angularjs/models/project.js new file mode 100644 index 000000000..22e587cc1 --- /dev/null +++ b/app/assets/javascripts/angularjs/models/project.js @@ -0,0 +1,14 @@ +var Project = function(atts){ + var self = this; + var initialSettings = atts || {}; + //initial settings if passed in + for(var setting in initialSettings){ + if(initialSettings.hasOwnProperty(setting)) + self[setting] = initialSettings[setting]; + }; + + //with some logic... + + //return the scope-safe instance + return self; +}; \ No newline at end of file diff --git a/app/assets/javascripts/angularjs/models/project_ref.js b/app/assets/javascripts/angularjs/models/project_ref.js new file mode 100644 index 000000000..3cae1999f --- /dev/null +++ b/app/assets/javascripts/angularjs/models/project_ref.js @@ -0,0 +1,25 @@ +var ProjectRef = function(atts) { + var self = this; + var initialSettings = atts || {}; + //initial settings if passed in + for(var setting in initialSettings){ + if(initialSettings.hasOwnProperty(setting)) + self[setting] = initialSettings[setting]; + }; + + + + //with some logic... + self.isTag = self.object.type == 'tag'; + + self.path = function(project) { + return '/' + project.fullname + '/tree/' + self.ref; + } + + self.diff_path = function(project, current_ref) { + return '/' + project.fullname + '/diff/' + current_ref + '...' + self.ref; + } + + //return the scope-safe instance + return self; +}; \ No newline at end of file diff --git a/app/assets/javascripts/angularjs/services/project.js b/app/assets/javascripts/angularjs/services/project.js new file mode 100644 index 000000000..82bde5b36 --- /dev/null +++ b/app/assets/javascripts/angularjs/services/project.js @@ -0,0 +1,30 @@ +var ApiProject = function($resource) { + + var projectResource = $resource('/api/v1/projects/:project_id.json'); + var queryProject = function(project_id, next) { + projectResource.get({project_id: project_id}, function(results){ + next(new Project(results.project)); + }); + }; + + var refsResource = $resource('/api/v1/projects/:project_id/refs_list.json'); + var queryRefs = function(project_id, next) { + //use a callback instead of a promise + refsResource.get({project_id: project_id}, function(results) { + var out = []; + //Underscore's "each" method + _.each(results.refs_list, function(ref){ + //using our ProjectRef(ref) prototype above + out.push(new ProjectRef(ref)); + }); + next(out); + }); + }; + + return { + refs : queryRefs, + project : queryProject + } +} + +RosaABF.factory("ApiProject", ApiProject); \ No newline at end of file diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 5a03bbe86..12af6a549 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,6 +13,11 @@ //= require backbone_rails_sync //= require backbone_datalink //= require backbone/rosa +//= require angular +//= require angular-resource +// require angularjs/rails/resource +//= require ng-rails-csrf +//= require_tree ./angularjs //= require_self function disableNotifierCbx(global_cbx) { diff --git a/app/controllers/projects/git/base_controller.rb b/app/controllers/projects/git/base_controller.rb index 2b854e2db..78da774e1 100644 --- a/app/controllers/projects/git/base_controller.rb +++ b/app/controllers/projects/git/base_controller.rb @@ -2,7 +2,11 @@ class Projects::Git::BaseController < Projects::BaseController before_filter :authenticate_user! skip_before_filter :authenticate_user!, :only => [:show, :index, :blame, :raw, :archive, :diff, :tags, :branches] if APP_CONFIG['anonymous_access'] - load_and_authorize_resource :project + + load_and_authorize_resource :project, :except => :destroy + load_resource :project, :only => :destroy + before_filter lambda { authorize!(:write, @project) }, :only => :destroy + before_filter :set_treeish_and_path before_filter :set_branch_and_tree diff --git a/app/controllers/projects/git/trees_controller.rb b/app/controllers/projects/git/trees_controller.rb index 775275318..c2d9d7b15 100644 --- a/app/controllers/projects/git/trees_controller.rb +++ b/app/controllers/projects/git/trees_controller.rb @@ -32,10 +32,26 @@ class Projects::Git::TreesController < Projects::Git::BaseController render 'refs' end + def destroy + render :nothing => true + end + def branches raise Grit::NoSuchPathError if params[:treeish] != @branch.try(:name) # get wrong branch name to nonempty project - @branches = @project.repo.branches.sort_by(&:name).select{ |b| b.name != @branch.name }.unshift(@branch).compact if @branch - render 'refs' + if request.xhr? + @branches = @project.repo.branches.sort_by(&:name).select{ |b| b.name != @branch.name }.unshift(@branch).compact if @branch + @branches = @branches.map do |branch| + { + :name => branch.name, + :path => tree_path(@project, branch.name), + :current => (branch.name == @branch.try(:name)), + :diff_path => diff_path(@project, "#{@branch.name}...#{branch.name}") + } + end + render :json => @branches + else + render 'refs' + end end end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b6cd5903b..754a2ada2 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -12,7 +12,7 @@ - if user_signed_in? = auto_discovery_link_tag :atom, atom_activity_feeds_path(:format => 'atom', :token => current_user.authentication_token), :title => t("layout.atom_link_tag_title", :nickname => current_user.uname, :app_name => APP_CONFIG['project_name']) - %body + %body{'ng-app' => 'RosaABF'} .wrap{:class => content_for?(:sidebar) ? 'columns' : ''} %header .left diff --git a/app/views/projects/git/trees/_header.html.haml b/app/views/projects/git/trees/_header.html.haml index edda53ee0..451651137 100644 --- a/app/views/projects/git/trees/_header.html.haml +++ b/app/views/projects/git/trees/_header.html.haml @@ -4,7 +4,7 @@ - if subject.blank? %p= t("layout.projects.no_#{subjects_name}") - elsif subject.count == 1 - %p= t("layout.projects.showing_#{subjects_name.singularize}") + %p= t("layout.projects.total_#{subjects_name.singularize}") - else - %p= t("layout.projects.showing_#{subjects_name}", :count => subject.count) + %p= t("layout.projects.total_#{subjects_name}", :count => subject.count) .both \ No newline at end of file diff --git a/app/views/projects/git/trees/refs.html.haml b/app/views/projects/git/trees/refs.html.haml index 586087c02..a047b0cc0 100644 --- a/app/views/projects/git/trees/refs.html.haml +++ b/app/views/projects/git/trees/refs.html.haml @@ -4,11 +4,35 @@ = render 'repo_block', :project => @project = render 'header', :subject => (@branches || @tags) + +%div{'ng-controller' => 'ProjectRefsController', + 'ng-init' => "init('#{@project.id}','#{@branch.try(:name)}')"} + + .both + Search: + %input{'ng-model' => 'query.ref'} + .both + + %table#project-branches + %tbody + %tr{'ng-repeat' => 'branch in branches | filter:query', 'ng-class' => '{base: branch.ref == current_ref}'} + %td.name + %a{'ng-href' => '{{branch.path(project)}}' } {{branch.ref}} + %td.actions + %ul.actions + %li.text{'ng-show' => 'branch.ref == current_ref'} + = t('layout.projects.base_branch') + %li{'ng-hide' => 'branch.ref == current_ref'} + %a{:href => '#', 'ng-click' => 'destroy(branch)'}= t('layout.projects.delete_branch') + %li{'ng-hide' => 'branch.ref == current_ref'} + %a{'ng-href' => '{{branch.diff_path(project, current_ref)}}' }= t('layout.projects.compare') + + - if @tags.present? %div#project-tags %ol.release-list = render :partial => 'tag', :collection => @tags -- elsif @branches.present? +- elsif false # @branches.present? %table#project-branches %tbody = render :partial => 'branch', :collection => @branches diff --git a/app/views/projects/pull_requests/_status.html.haml b/app/views/projects/pull_requests/_status.html.haml index de98651d4..5a2f4ed59 100644 --- a/app/views/projects/pull_requests/_status.html.haml +++ b/app/views/projects/pull_requests/_status.html.haml @@ -6,6 +6,7 @@ .flash %div{:class => @pull.ready? ? 'notice' : 'alert'} =pull_status @pull + wwwww -if can? :update, @pull -if action = @pull.can_close? ? 'close' : ('reopen' if @pull.can_reopen?) %br diff --git a/config/locales/models/project.en.yml b/config/locales/models/project.en.yml index a5e31b151..4d744e598 100644 --- a/config/locales/models/project.en.yml +++ b/config/locales/models/project.en.yml @@ -2,16 +2,17 @@ en: layout: projects: branches: Branches - showing_branches: Showing %{count} branches - showing_branch: Showing 1 branch + delete_branch: Delete branch + total_branches: Total %{count} branches + total_branch: Total 1 branch no_branches: No branches base_branch: Base branch compare: Compare browse_code: Browse code source_code: Source code (%{type}) tags: Tags - showing_tags: Showing %{count} tags - showing_tag: Showing 1 tag + total_tags: Total %{count} tags + total_tag: Total 1 tag no_tags: No tags add: Add public_projects_list: Public projects list diff --git a/config/locales/models/project.ru.yml b/config/locales/models/project.ru.yml index 2d5365e30..446019780 100644 --- a/config/locales/models/project.ru.yml +++ b/config/locales/models/project.ru.yml @@ -2,16 +2,17 @@ ru: layout: projects: branches: Ветки - showing_branches: Показано %{count} веток - showing_branch: Показана 1 ветка + delete_branch: Удалить ветку + total_branches: Всего %{count} веток + total_branch: Всего 1 ветка no_branch: Нет веток base_branch: Текущая ветка compare: Сравнить browse_code: Просмотреть код source_code: Исходный код (%{type}) tags: Теги - showing_tags: Показано %{count} тегов - showing_tag: Показан 1 тег + total_tags: Всего %{count} тегов + total_tag: Всего 1 тег no_tags: Нет тегов add: Добавить public_projects_list: Список публичных проектов diff --git a/config/routes.rb b/config/routes.rb index 5c5710fbf..907326ea0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -331,8 +331,10 @@ Rosa::Application.routes.draw do get '/tree/:treeish(/*path)' => "git/trees#show", :as => :tree, :format => false # Tags get '/tags' => "git/trees#tags", :as => :tags + # delete '/tags/:treeish' => "git/trees#destroy", :as => :tags # Branches get '/branches/:treeish' => "git/trees#branches", :as => :branches + # delete '/branches/:treeish' => "git/trees#destroy", :as => :branches # Commits get '/commits/:treeish(/*path)' => "git/commits#index", :as => :commits, :format => false get '/commit/:id(.:format)' => "git/commits#show", :as => :commit