Commit e6ce4729 authored by Alex Denisov's avatar Alex Denisov

master merged

parents 77bfc591 61049424
## Contribute to GitLab
If you want to contribute to GitLab, follow this process:
1. Fork the project
2. Create a feature branch
3. Code
4. Create a pull request
We only accept pull requests if:
* Your code has proper tests and all tests pass
* Your code can be merged w/o problems
* It wont broke existing functionality
* Its a quality code
* We like it :)
## [You may need a developer VM](https://github.com/gitlabhq/developer-vm)
## Running tests
To run the specs for GitLab, you need to run seeds for test db.
cd gitlabhq
rake db:seed_fu RAILS_ENV=test
Then you can run the test suite with rake:
rake gitlab:test
source "http://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
end
def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem "rails", "3.2.8"
# Supported DBs
......@@ -8,6 +16,10 @@ gem "mysql2"
# Auth
gem "devise", "~> 2.1.0"
gem 'omniauth'
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
# GITLAB patched libs
gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
......@@ -98,21 +110,28 @@ group :development do
end
group :development, :test do
gem 'spinach-rails'
gem "rspec-rails"
gem "capybara"
gem "capybara-webkit"
gem "headless"
gem "autotest"
gem "autotest-rails"
gem "pry"
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
gem 'factory_girl_rails'
# Guard
gem 'guard-rspec'
gem 'guard-spinach'
# Notification
gem 'rb-fsevent', :require => darwin_only('rb-fsevent')
gem 'growl', :require => darwin_only('growl')
gem 'rb-inotify', :require => linux_only('rb-inotify')
end
group :test do
gem 'cucumber-rails', :require => false
gem "simplecov", :require => false
gem "shoulda-matchers"
gem 'email_spec'
......
......@@ -68,7 +68,6 @@ GIT
GEM
remote: http://rubygems.org/
specs:
ZenTest (4.8.1)
actionmailer (3.2.8)
actionpack (= 3.2.8)
mail (~> 2.4.4)
......@@ -100,10 +99,6 @@ GEM
rails (~> 3.0)
addressable (2.2.8)
arel (3.0.2)
autotest (4.4.6)
ZenTest (>= 4.4.1)
autotest-rails (4.1.2)
ZenTest (~> 4.5)
awesome_print (1.0.2)
bcrypt-ruby (3.0.1)
blankslate (2.1.2.4)
......@@ -137,16 +132,8 @@ GEM
execjs
coffee-script-source (1.3.3)
colored (1.2)
colorize (0.5.8)
crack (0.3.1)
cucumber (1.2.1)
builder (>= 2.1.2)
diff-lcs (>= 1.1.3)
gherkin (~> 2.11.0)
json (>= 1.4.6)
cucumber-rails (1.3.0)
capybara (>= 1.1.2)
cucumber (>= 1.1.8)
nokogiri (>= 1.5.0)
daemons (1.1.8)
database_cleaner (0.8.0)
devise (2.1.2)
......@@ -171,12 +158,13 @@ GEM
factory_girl_rails (4.0.0)
factory_girl (~> 4.0.0)
railties (>= 3.0.0)
faraday (0.8.4)
multipart-post (~> 1.1)
ffaker (1.14.0)
ffi (1.0.11)
foreman (0.47.0)
thor (>= 0.13.6)
gherkin (2.11.0)
json (>= 1.4.6)
gherkin-ruby (0.2.1)
git (1.2.5)
github-markup (0.7.4)
gitlab_meta (2.9)
......@@ -186,6 +174,15 @@ GEM
multi_xml
rack
rack-mount
growl (1.0.3)
guard (1.3.2)
listen (>= 0.4.2)
thor (>= 0.14.6)
guard-rspec (1.2.1)
guard (>= 1.1)
guard-spinach (0.0.2)
guard (>= 1.1)
spinach
haml (3.1.6)
haml-rails (0.3.4)
actionpack (~> 3.0)
......@@ -199,6 +196,7 @@ GEM
httparty (0.8.3)
multi_json (~> 1.0)
multi_xml
httpauth (0.1)
i18n (0.6.1)
journey (1.0.4)
jquery-rails (2.0.2)
......@@ -208,6 +206,8 @@ GEM
jquery-rails
railties (>= 3.1.0)
json (1.7.5)
jwt (0.1.5)
multi_json (>= 1.0)
kaminari (0.14.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
......@@ -219,6 +219,7 @@ GEM
libv8 (3.3.10.4)
libwebsocket (0.1.3)
addressable
listen (0.5.0)
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
......@@ -229,12 +230,35 @@ GEM
sprockets (~> 2.0)
multi_json (1.3.6)
multi_xml (0.5.1)
multipart-post (1.1.5)
mysql2 (0.3.11)
net-ldap (0.2.2)
nokogiri (1.5.3)
oauth (0.4.7)
oauth2 (0.8.0)
faraday (~> 0.8)
httpauth (~> 0.1)
jwt (~> 0.1.4)
multi_json (~> 1.0)
rack (~> 1.2)
omniauth (1.1.0)
hashie (~> 1.2)
rack
omniauth-github (1.0.3)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1)
omniauth-google-oauth2 (0.1.13)
omniauth (~> 1.0)
omniauth-oauth2
omniauth-oauth (1.0.1)
oauth
omniauth (~> 1.0)
omniauth-oauth2 (1.1.0)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
omniauth-twitter (0.0.13)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
orm_adapter (0.3.0)
polyglot (0.3.3)
posix-spawn (0.3.6)
......@@ -274,6 +298,9 @@ GEM
raindrops (0.9.0)
rake (0.9.2.2)
raphael-rails (1.5.2)
rb-fsevent (0.9.1)
rb-inotify (0.8.8)
ffi (>= 0.5.0)
rdoc (3.12)
json (~> 1.4)
redcarpet (2.1.1)
......@@ -336,6 +363,13 @@ GEM
tilt (~> 1.3, >= 1.3.3)
six (0.2.0)
slop (2.4.4)
spinach (0.5.2)
colorize
gherkin-ruby (~> 0.2.0)
spinach-rails (0.1.8)
capybara (~> 1)
railties (>= 3)
spinach (>= 0.4)
sprockets (2.1.3)
hike (~> 1.2)
rack (~> 1.0)
......@@ -378,8 +412,6 @@ PLATFORMS
DEPENDENCIES
acts-as-taggable-on (= 2.3.1)
annotate!
autotest
autotest-rails
awesome_print
bootstrap-sass (= 2.0.4)
capybara
......@@ -389,7 +421,6 @@ DEPENDENCIES
chosen-rails
coffee-rails (= 3.2.2)
colored
cucumber-rails
database_cleaner
devise (~> 2.1.0)
draper
......@@ -404,6 +435,9 @@ DEPENDENCIES
grack!
grape (~> 0.2.1)
grit!
growl
guard-rspec
guard-spinach
haml-rails
headless
httparty
......@@ -415,12 +449,18 @@ DEPENDENCIES
linguist (~> 1.0.0)!
modernizr (= 2.5.3)
mysql2
omniauth
omniauth-github
omniauth-google-oauth2
omniauth-ldap!
omniauth-twitter
pry
pygments.rb!
rack-mini-profiler
rails (= 3.2.8)
raphael-rails (= 1.5.2)
rb-fsevent
rb-inotify
redcarpet (~> 2.1.1)
resque (~> 1.20.0)
resque_mailer
......@@ -432,6 +472,7 @@ DEPENDENCIES
shoulda-matchers
simplecov
six
spinach-rails
sqlite3
stamp
test_after_commit
......
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }
# Rails example
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
watch('config/routes.rb') { "spec/routing" }
watch('app/controllers/application_controller.rb') { "spec/controllers" }
# Capybara request specs
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
end
guard 'spinach' do
watch(%r|^features/(.*)\.feature|)
watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
"features/#{m[1]}#{m[2]}.feature"
end
end
......@@ -6,3 +6,7 @@ $ ->
elems.val('').attr 'disabled', true
else
elems.removeAttr 'disabled'
$('.log-tabs a').click (e) ->
e.preventDefault()
$(this).tab('show')
......@@ -11,7 +11,7 @@
//= require jquery.endless-scroll
//= require jquery.highlight
//= require jquery.waitforimages
//= require bootstrap-modal
//= require bootstrap
//= require modernizr
//= require chosen-jquery
//= require raphael
......
......@@ -24,6 +24,9 @@ $ ->
# Click a .one_click_select field, select the contents
$(".one_click_select").live 'click', -> $(this).select()
# Initialize chosen selects
$('select.chosen').chosen()
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', this)
......
var NoteList = {
notes_path: null,
target_params: null,
target_id: 0,
target_type: null,
first_id: 0,
last_id: 0,
disable:false,
init:
function(tid, tt, path) {
this.notes_path = path + ".js";
this.target_id = tid;
this.target_type = tt;
this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
// get notes
this.getContent();
// get new notes every n seconds
this.initRefresh();
$('.delete-note').live('ajax:success', function() {
$(this).closest('li').fadeOut(); });
$(".note-form-holder").live("ajax:before", function(){
$(".submit_note").disable()
})
$(".note-form-holder").live("ajax:complete", function(){
$(".submit_note").enable()
})
disableButtonIfEmptyField(".note-text", ".submit_note");
$(".note-text").live("focus", function(){
$(this).css("height", "80px");
$('.note_advanced_opts').show();
});
$("#note_attachment").change(function(e){
var val = $('.input-file').val();
var filename = val.replace(/^.*[\\\/]/, '');
$(".file_name").text(filename);
});
},
/**
* Load new notes to fresh list called 'new_notes_list':
* - Replace 'new_notes_list' with new list every n seconds
* - Append new notes to this list after submit
*/
initRefresh:
function() {
// init timer
var intNew = setInterval("NoteList.getNew()", 10000);
},
replace:
function(html) {
$("#new_notes_list").html(html);
},
prepend:
function(id, html) {
if(id != this.last_id) {
$("#new_notes_list").prepend(html);
}
},
getNew:
function() {
// refersh notes list
$.ajax({
type: "GET",
url: this.notes_path,
data: "last_id=" + this.last_id + this.target_params,
dataType: "script"});
},
refresh:
function() {
// refersh notes list
$.ajax({
type: "GET",
url: this.notes_path,
data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params,
dataType: "script"});
},
/**
* Init load of notes:
* 1. Get content with ajax call
* 2. Set content of notes list with loaded one
*/
getContent:
function() {
$.ajax({
type: "GET",
url: this.notes_path,
data: "?" + this.target_params,
complete: function(){ $('.status').removeClass("loading")},
beforeSend: function() { $('.status').addClass("loading") },
dataType: "script"});
},
setContent:
function(fid, lid, html) {
this.last_id = lid;
this.first_id = fid;
$("#notes-list").html(html);
// Init infinite scrolling
this.initLoadMore();
},
/**
* Paging for old notes when scroll to bottom:
* 1. Init scroll events with 'initLoadMore'
* 2. Load onlder notes with 'getOld' method
* 3. append old notes to bottom of list with 'append'
*
*/
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: this.notes_path,
data: "first_id=" + this.first_id + this.target_params,
complete: function(){ $('.status').removeClass("loading")},
beforeSend: function() { $('.status').addClass("loading") },
dataType: "script"});
},
append:
function(id, html) {
if(this.first_id == id) {
this.disable = true;
} else {
this.first_id = id;
$("#notes-list").append(html);
}
},
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return NoteList.disable;
},
callback: function(i) {
NoteList.getOld();
}
});
}
};
var PerLineNotes = {
init:
function() {
$(".line_note_link, .line_note_reply_link").live("click", function(e) {
var form = $(".per_line_form");
$(this).closest("tr").after(form);
form.find("#note_line_code").val($(this).attr("line_code"));
form.show();
return false;
});
disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
}
}
var NoteList = {
notes_path: null,
target_params: null,
target_id: 0,
target_type: null,
top_id: 0,
bottom_id: 0,
loading_more_disabled: false,
reversed: false,
init:
function(tid, tt, path) {
this.notes_path = path + ".js";
this.target_id = tid;
this.target_type = tt;
this.reversed = $("#notes-list").hasClass("reversed");
this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
// get initial set of notes
this.getContent();
$("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() {
$(this).closest('li').fadeOut(function() {
$(this).remove();
NoteList.updateVotes();
});
});
$(".note-form-holder").on("ajax:before", function(){
$(".submit_note").disable();
})
$(".note-form-holder").on("ajax:complete", function(){
$(".submit_note").enable();
})
disableButtonIfEmptyField(".note-text", ".submit_note");
$("#note_attachment").change(function(e){
var val = $('.input-file').val();
var filename = val.replace(/^.*[\\\/]/, '');
$(".file_name").text(filename);
});
if(this.reversed) {
var textarea = $(".note-text");
$('.note_advanced_opts').hide();
textarea.css("height", "40px");
textarea.on("focus", function(){
$(this).css("height", "80px");
$('.note_advanced_opts').show();
});
}
},
/**
* Handle loading the initial set of notes.
* And set up loading more notes when scrolling to the bottom of the page.
*/
/**
* Gets an inital set of notes.
*/
getContent:
function() {
$.ajax({
type: "GET",
url: this.notes_path,
data: "?" + this.target_params,
complete: function(){ $('.notes-status').removeClass("loading")},
beforeSend: function() { $('.notes-status').addClass("loading") },
dataType: "script"});
},
/**
* Called in response to getContent().
* Replaces the content of #notes-list with the given html.
*/
setContent:
function(first_id, last_id, html) {
this.top_id = first_id;
this.bottom_id = last_id;
$("#notes-list").html(html);
// init infinite scrolling
this.initLoadMore();
// init getting new notes
if (this.reversed) {
this.initRefreshNew();
}
},
/**
* Handle loading more notes when scrolling to the bottom of the page.
* The id of the last note in the list is in this.bottom_id.
*
* Set up refreshing only new notes after all notes have been loaded.
*/
/**
* Initializes loading more notes when scrolling to the bottom of the page.
*/
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return NoteList.loading_more_disabled;
},
callback: function(i) {
NoteList.getMore();
}
});
},
/**
* Gets an additional set of notes.
*/
getMore:
function() {
// only load more notes if there are no "new" notes
$('.loading').show();
$.ajax({
type: "GET",
url: this.notes_path,
data: "loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id + this.target_params,
complete: function(){ $('.notes-status').removeClass("loading")},
beforeSend: function() { $('.notes-status').addClass("loading") },
dataType: "script"});
},
/**
* Called in response to getMore().
* Append notes to #notes-list.
*/
appendMoreNotes:
function(id, html) {
if(id != this.bottom_id) {
this.bottom_id = id;
$("#notes-list").append(html);
}
},
/**
* Called in response to getMore().
* Disables loading more notes when scrolling to the bottom of the page.
* Initalizes refreshing new notes.
*/
finishedLoadingMore:
function() {
this.loading_more_disabled = true;
// from now on only get new notes
if (!this.reversed) {
this.initRefreshNew();
}
// make sure we are up to date
this.updateVotes();
},
/**
* Handle refreshing and adding of new notes.
*
* New notes are all notes that are created after the site has been loaded.
* The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
* The id of the last "old" note is in this.bottom_id.
*/
/**
* Initializes getting new notes every n seconds.
*/
initRefreshNew:
function() {
setInterval("NoteList.getNew()", 10000);
},
/**
* Gets the new set of notes.
*/
getNew:
function() {
$.ajax({
type: "GET",
url: this.notes_path,
data: "loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id) + this.target_params,
dataType: "script"});
},
/**
* Called in response to getNew().
* Replaces the content of #new-notes-list with the given html.
*/
replaceNewNotes:
function(html) {
$("#new-notes-list").html(html);
this.updateVotes();
},
/**
* Adds a single note to #new-notes-list.
*/
appendNewNote:
function(id, html) {
if (this.reversed) {
$("#new-notes-list").prepend(html);
} else {
$("#new-notes-list").append(html);
}
this.updateVotes();
},
/**
* Recalculates the votes and updates them (if they are displayed at all).
*
* Assumes all relevant notes are displayed (i.e. there are no more notes to
* load via getMore()).
* Might produce inaccurate results when not all notes have been loaded and a
* recalculation is triggered (e.g. when deleting a note).
*/
updateVotes:
function() {
var votes = $("#votes .votes");
var notes = $("#notes-list, #new-notes-list").find(".note.vote");
// only update if there is a vote display
if (votes.size()) {
var upvotes = notes.filter(".upvote").size();
var downvotes = notes.filter(".downvote").size();
var votesCount = upvotes + downvotes;
var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;
// change vote bar lengths
votes.find(".bar-success").css("width", upvotesPercent+"%");
votes.find(".bar-danger").css("width", downvotesPercent+"%");
// replace vote numbers
votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
}
}
};
var PerLineNotes = {
init:
function() {
/**
* Called when clicking on the "add note" or "reply" button for a diff line.
*
* Shows the note form below the line.
* Sets some hidden fields in the form.
*/
$(".diff_file_content").on("click", ".line_note_link, .line_note_reply_link", function(e) {
var form = $(".per_line_form");
$(this).closest("tr").after(form);
form.find("#note_line_code").val($(this).data("lineCode"));
form.show();
return false;
});
disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
/**
* Called in response to successfully deleting a note on a diff line.
*
* Removes the actual note from view.
* Removes the reply button if the last note for that line has been removed.
*/
$(".diff_file_content").on("ajax:success", ".delete-note", function() {
var trNote = $(this).closest("tr");
trNote.fadeOut(function() {
$(this).remove();
});
// check if this is the last note for this line
// elements must really be removed for this to work reliably
var trLine = trNote.prev();
var trRpl = trNote.next();
if (trLine.hasClass("line_holder") && trRpl.hasClass("reply")) {
trRpl.fadeOut(function() { $(this).remove(); });
}
});
}
}
......@@ -10,11 +10,15 @@ window.Projects = ->
$('form #project_default_branch').chosen()
disableButtonIfEmptyField '#project_name', '.project-submit'
# Git clone panel switcher
$ ->
# Git clone panel switcher
scope = $ '.project_clone_holder'
if scope.length > 0
$('a, button', scope).click ->
$('a, button', scope).removeClass 'active'
$(@).addClass 'active'
$('#project_clone', scope).val $(@).data 'clone'
# Ref switcher
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
......@@ -145,6 +145,19 @@ span.update-author {
.label {
background-color: #474D57;
&.label-tag {
background: none;
border: none;
padding:4px 6px;
color:#444;
text-shadow:0 0 1px #fff;
&.grouped {
float: left;
margin-right: 6px;
padding: 6px;
}
}
&.label-issue {
background-color: #eee;
border: 1px solid #ccc;
......@@ -158,6 +171,18 @@ span.update-author {
padding: 6px;
}
}
&.label-success {
background-color: #8D8;
color: #333;
text-shadow: 0 1px 1px white;
}
&.label-error {
background-color: #D88;
color: #333;
text-shadow: 0 1px 1px white;
}
}
.event_label {
......@@ -181,11 +206,12 @@ span.update-author {
}
&.joined {
background-color: #1cb9ff;
background-color: #1ca9dd;
}
&.left {
background-color: #ff5057;
background-color: #888;
float:none;
}
}
......@@ -414,13 +440,48 @@ p.time {
}
}
.upvotes {
font-size: 14px;
font-weight: bold;
color: #468847;
text-align: right;
padding: 4px;
margin: 2px;
.votes {
font-size: 13px;
line-height: 15px;
.progress {
height: 4px;
margin: 0;
.bar {
float: left;
height: 100%;
}
.bar-success {
background-color: #468847;
@include bg-gradient(#62C462, #51A351);
}
.bar-danger {
background-color: #B94A48;
@include bg-gradient(#EE5F5B, #BD362F);
}
}
.upvotes {
display: inline-block;
color: #468847;
}
.downvotes {
display: inline-block;
color: #B94A48;
}
}
.votes-block {
margin: 14px 6px 6px 0;
.downvotes {
float: right;
}
}
.votes-inline {
display: inline-block;
margin: 0 8px;
.progress {
display: inline-block;
padding: 0 0 2px;
width: 45px;
}
}
/* Fix for readme code (stopped it from being yellow) */
......@@ -624,7 +685,7 @@ li.note {
margin-right:40px;
.prev {
@extend .borders;
@extend .thumbnail;
height:120px;
width:175px;
margin-bottom:10px;
......@@ -653,3 +714,31 @@ li.note {
text-align:center;
margin-bottom:10px;
}
.oauth_select_holder {
padding:20px;
img {
padding:5px;
margin-right:10px;
}
.active {
img {
border:1px solid #ccc;
background:$hover;
@include border-radius(5px);
}
}
}
.btn-build-token {
float: left;
padding: 6px 20px;
margin-right: 12px;
}
.gitlab-promo {
a {
color:#aaa;
margin-right: 30px;
}
}
......@@ -65,6 +65,10 @@
border-color: #CCC;
@include solid_shade;
&.white {
background:#fff;
}
ul {
margin:0;
}
......@@ -142,4 +146,8 @@
border:none;
}
}
.ui-box-body {
padding:10px;
}
}
......@@ -33,7 +33,29 @@
.nav-pills a:hover { background-color:#888; }
.nav-pills .active a { background-color: $style_color; }
.nav-tabs > li > a, .nav-pills > li > a { color:$style_color; }
.nav-tabs > .active > a { font-weight:bold; }
.nav.nav-tabs {
li {
> a {
padding:8px 20px;
margin-right: 7px;
border-color: #EEE;
color:#888;
border-bottom: 1px solid #ddd;
.badge {
background-color: #eee;
color:#888;
text-shadow:0 1px 1px #fff;
}
}
&.active {
> a {
border-color: #CCC;
border-bottom: 1px solid #fff;
color:#333;
}
}
}
}
/** ALERT MESSAGES **/
.alert-message { @extend .alert; }
......@@ -50,3 +72,13 @@ img.lil_av { padding-left: 4px; padding-right:3px; }
/** HELPERS **/
.nothing_here_message { text-align:center; padding:20px; color:#777; }
p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; }
/** FORMS **/
input[type='search'].search-text-input {
background-image: url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
padding-left:25px;
@include border-radius(4px);
border:1px solid #ccc;
}
......@@ -135,7 +135,6 @@ $hover: #fdf5d9;
*/
@import "common.scss";
/**
* Styles related to specific part of app
*/
......@@ -161,6 +160,11 @@ $hover: #fdf5d9;
*/
@import "sections/notes.scss";
/**
* This file represent profile styles
*/
@import "sections/profile.scss";
/**
* Devise styles
*/
......
......@@ -12,35 +12,45 @@
width:120px;
}
.project-refs-form .chzn-container {
.project-refs-form .chzn-container {
position: relative;
top: 0;
left: 0;
margin-right: 10px;
.chzn-drop {
.chzn-drop {
margin:7px 0;
border: 1px solid #CCC;
min-width: 300px;
min-width: 400px;
border: 2px solid $blue_link;
@include border-radius(4px);
.chzn-results {
.chzn-results {
max-height:300px;
.group-result {
color: $blue_link;
}
.active-result {
&.highlighted {
background: $blue_link;
}
}
}
.chzn-search input {
min-width:200px;
min-width:365px;
}
}
.chzn-single {
.chzn-single {
@include bg-gray-gradient;
div {
div {
background:transparent;
border-left:none;
}
span {
span {
font-weight: normal;
}
}
......
.issue_form_box {
.issue_form_box {
@extend .main_box;
.issue_title {
.issue_title {
@extend .top_box_content;
.clearfix {
margin-bottom:0px;
input {
.clearfix {
margin-bottom:0px;
input {
@extend .span8;
}
}
}
.issue_middle_block {
.issue_middle_block {
@extend .middle_box_content;
height:30px;
.issue_assignee {
.issue_assignee {
@extend .span6;
float:left;
}
.issue_milestone {
.issue_milestone {
@extend .span4;
float:left;
}
}
.issue_description {
.issue_description {
@extend .bottom_box_content;
}
}
.issues_table {
.issue {
.issues_table {
.issue {
padding:7px 10px;
.issue_check {
.issue_check {
float:left;
padding: 8px 0;
padding-right: 8px;
min-width: 15px;
}
p {
p {
padding-top:0;
padding-bottom:2px;
}
img.avatar {
img.avatar {
width:32px;
margin-top:4px;
}
}
}
input.check_all_issues {
input.check_all_issues {
float:left;
padding: 0;
margin:0;
......@@ -59,8 +59,8 @@ input.check_all_issues {
height: 22px;
}
.issues_content {
.title {
.issues_content {
.title {
height: 40px;
}
}
......@@ -70,30 +70,30 @@ input.check_all_issues {
@media (min-width: 1200px) { .issues_filters select { width:220px; } }
#issues-table-holder {
.issues_filters {
form {
#issues-table-holder {
.issues_filters {
form {
padding:0;
margin:0;
margin-top:7px
}
}
}
.issues_bulk_update {
.issues_bulk_update {
margin: 0;
form {
form {
padding:0;
margin:0;
margin-top:7px
}
.update_selected_issues {
.update_selected_issues {
position:relative;
top:-2px;
margin-left:4px;
float:left;
}
.update_issues_text {
.update_issues_text {
padding:3px;
line-height: 18px;
float:left;
......@@ -101,10 +101,11 @@ input.check_all_issues {
}
}
#update_status {
#update_status {
width:100px;
}
/**
* Milestones list
*
......
/**
/**
* MR form
*
*/
.mr_branch_box {
.mr_branch_box {
@extend .ui-box;
margin-bottom:20px;
.body {
.body {
background:#f1f1f1;
}
......@@ -17,19 +17,19 @@
* MR -> show: Automerge widget
*
*/
.automerge_widget {
&.can_be_merged {
.automerge_widget {
&.can_be_merged {
background: #DFF0D8;
}
form {
form {
margin-bottom:0;
.clearfix {
.clearfix {
margin-bottom:0;
}
}
.accept_group {
.accept_group {
float:left;
border: 1px solid #ADA;
padding: 2px;
......@@ -37,29 +37,29 @@
border-radius: 5px;
background: #CEB;
.accept_merge_request {
.accept_merge_request {
font-size:13px;
float:left;
}
.remove_branch_holder {
.remove_branch_holder {
margin-left:20px;
margin-right:10px;
float:left;
}
label {
label {
color:#444;
}
}
.how_to_merge_link {
.how_to_merge_link {
@extend .primary;
}
}
.mr_nav_tabs {
li {
a {
.mr_nav_tabs {
li {
a {
font-weight:bold;
padding:8px 20px;
text-align:center;
......@@ -67,19 +67,19 @@
}
}
li.merge_request {
li.merge_request {
padding:7px 10px;
img.avatar {
img.avatar {
width: 32px;
margin-top: 4px;
}
p {
p {
padding: 0px;
padding-bottom: 2px;
}
}
.merge_in_progress {
.merge_in_progress {
@extend .padded;
@extend .append-bottom-10;
}
......@@ -88,22 +88,21 @@ li.merge_request {
@include round-borders-all(4px);
padding:2px 4px;
border:none;
font-size:13px;
font-size:14px;
background: #474D57;
color:#fff;
font-weight:bold;
font-family: monospace;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
}
.mr_source_commit,
.mr_target_commit {
.commit {
.mr_source_commit,
.mr_target_commit {
.commit {
margin:0;
padding:0;
padding: 5px;
margin-bottom: 5px;
.avatar { position:relative }
.row_title {
.row_title {
color:#444;
}
.commit-author-name,
......@@ -113,12 +112,12 @@ li.merge_request {
display:none;
}
list-style:none;
&:hover {
&:hover {
background:none;
}
}
}
.mr_direction_tip {
.mr_direction_tip {
margin-top:40px
}
......@@ -55,7 +55,6 @@ ul.main_menu {
&.current {
background-color:#D5D5D5;
border-bottom: 1px solid #AAA;
border-right: 1px solid #BBB;
border-left: 1px solid #BBB;
border-radius: 0 0 1px 1px;
......
......@@ -3,17 +3,13 @@
*
*/
#notes-list,
#new_notes_list {
#new-notes-list {
display:block;
list-style:none;
margin:0px;
padding:0px;
}
#new_notes_list li:last-child{
border-bottom:1px solid #aaa;
}
.issue_notes,
.wiki_notes {
.note_content {
......@@ -30,9 +26,6 @@
}
#new_note {
.note-text {
height:40px;
}
.attach_holder {
display:none;
}
......@@ -48,7 +41,6 @@
.note {
padding: 8px 0;
border-bottom: 1px solid #eee;
overflow: hidden;
display: block;
img {float: left; margin-right: 10px;}
......@@ -70,6 +62,23 @@
.delete-note { display:block; }
}
}
#notes-list:not(.reversed) .note,
#new-notes-list:not(.reversed) .note {
border-bottom: 1px solid #eee;
}
#notes-list.reversed .note,
#new-notes-list.reversed .note {
border-top: 1px solid #eee;
}
/* mark vote notes */
.voting_notes .note {
padding: 8px 0;
}
.notes-status {
margin: 18px;
}
p.notify_controls input{
......@@ -213,7 +222,7 @@ td .line_note_link {
}
}
.note-text {
.note-text {
border: 1px solid #aaa;
box-shadow:none;
}
.profile_history {
.event_feed {
min-height:20px;
.avatar {
width:20px;
}
}
}
......@@ -3,30 +3,31 @@ module Notes
def execute
target_type = params[:target_type]
target_id = params[:target_id]
first_id = params[:first_id]
last_id = params[:last_id]
after_id = params[:after_id]
before_id = params[:before_id]
@notes = case target_type
when "commit"
then project.commit_notes(project.commit(target_id)).fresh.limit(20)
when "snippet"
then project.snippets.find(target_id).notes
when "wall"
then project.common_notes.order("created_at DESC").fresh.limit(50)
when "commit"
project.commit_notes(project.commit(target_id)).fresh.limit(20)
when "issue"
then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
project.issues.find(target_id).notes.inc_author.fresh.limit(20)
when "merge_request"
then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
project.merge_requests.find(target_id).notes.inc_author.fresh.limit(20)
when "snippet"
project.snippets.find(target_id).notes.fresh
when "wall"
# this is the only case, where the order is DESC
project.common_notes.order("created_at DESC, id DESC").limit(50)
when "wiki"
then project.wikis.reverse.map {|w| w.notes.fresh }.flatten[0..20]
project.wiki_notes.limit(20)
end
@notes = if last_id
@notes.where("id > ?", last_id)
elsif first_id
@notes.where("id < ?", first_id)
else
@notes = if after_id
@notes.where("id > ?", after_id)
elsif before_id
@notes.where("id < ?", before_id)
else
@notes
end
end
......
class Admin::DashboardController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::DashboardController < AdminController
def index
@workers = Resque.workers
@pending_jobs = Resque.size(:post_receive)
......
class Admin::HooksController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::HooksController < AdminController
def index
@hooks = SystemHook.all
@hook = SystemHook.new
......@@ -15,7 +11,7 @@ class Admin::HooksController < ApplicationController
redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
else
@hooks = SystemHook.all
render :index
render :index
end
end
......
class Admin::LogsController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::LogsController < AdminController
end
class Admin::ProjectsController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::ProjectsController < AdminController
before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update]
def index
......@@ -43,7 +40,7 @@ class Admin::ProjectsController < ApplicationController
def update
owner_id = params[:project].delete(:owner_id)
if owner_id
if owner_id
@admin_project.owner = User.find(owner_id)
end
......@@ -60,7 +57,7 @@ class Admin::ProjectsController < ApplicationController
redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
end
private
private
def admin_project
@admin_project = Project.find_by_code(params[:id])
......
class Admin::ResqueController < ApplicationController
layout 'admin'
class Admin::ResqueController < AdminController
def show
end
end
\ No newline at end of file
end
class Admin::TeamMembersController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::TeamMembersController < AdminController
def edit
@admin_team_member = UsersProject.find(params[:id])
end
......
class Admin::UsersController < ApplicationController
layout "admin"
before_filter :authenticate_user!
before_filter :authenticate_admin!
class Admin::UsersController < AdminController
def index
@admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter])
......@@ -24,7 +20,7 @@ class Admin::UsersController < ApplicationController
@admin_user = User.find(params[:id])
UsersProject.user_bulk_import(
@admin_user,
@admin_user,
params[:project_ids],
params[:project_access]
)
......@@ -41,22 +37,22 @@ class Admin::UsersController < ApplicationController
@admin_user = User.find(params[:id])
end
def block
def block
@admin_user = User.find(params[:id])
if @admin_user.block
redirect_to :back, alert: "Successfully blocked"
else
else
redirect_to :back, alert: "Error occured. User was not blocked"
end
end
def unblock
def unblock
@admin_user = User.find(params[:id])
if @admin_user.update_attribute(:blocked, false)
redirect_to :back, alert: "Successfully unblocked"
else
else
redirect_to :back, alert: "Error occured. User was not unblocked"
end
end
......
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class AdminController < ApplicationController
layout 'admin'
before_filter :authenticate_admin!
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
end
......@@ -84,10 +84,6 @@ class ApplicationController < ActionController::Base
abilities << Ability
end
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
......
......@@ -64,7 +64,7 @@ class CommitsController < ApplicationController
@commit.to_patch,
type: "text/plain",
disposition: 'attachment',
filename: "#{@commit.id.patch}"
filename: "#{@commit.id}.patch"
)
end
......
......@@ -17,7 +17,7 @@ class IssuesController < ApplicationController
before_filter :authorize_write_issue!, only: [:new, :create]
# Allow modify issue
before_filter :authorize_modify_issue!, only: [:close, :edit, :update]
before_filter :authorize_modify_issue!, only: [:edit, :update]
# Allow destroy issue
before_filter :authorize_admin_issue!, only: [:destroy]
......@@ -87,8 +87,6 @@ class IssuesController < ApplicationController
end
def destroy
return access_denied! unless can?(current_user, :admin_issue, @issue)
@issue.destroy
respond_to do |format|
......
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
Gitlab.config.omniauth_providers.each do |provider|
define_method provider['name'] do
handle_omniauth
end
end
# Extend the standard message generation to accept our custom exception
def failure_message
......@@ -9,7 +14,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error ||= env["omniauth.error.type"].to_s
error.to_s.humanize if error
end
def ldap
# We only find ourselves here if the authentication to LDAP was successful.
@user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
......@@ -19,4 +24,27 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
sign_in_and_redirect @user
end
private
def handle_omniauth
oauth = request.env['omniauth.auth']
provider, uid = oauth['provider'], oauth['uid']
if current_user
# Change a logged-in user's authentication method:
current_user.extern_uid = uid
current_user.provider = provider
current_user.save
redirect_to profile_path
else
@user = User.find_or_new_for_omniauth(oauth)
if @user
sign_in_and_redirect @user
else
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end
end
end
end
......@@ -16,9 +16,6 @@ class ProfileController < ApplicationController
def token
end
def password
end
def password_update
params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"}
......@@ -32,10 +29,14 @@ class ProfileController < ApplicationController
def reset_private_token
current_user.reset_authentication_token!
redirect_to profile_token_path
redirect_to profile_account_path
end
def history
@events = current_user.recent_events.page(params[:page]).per(20)
end
private
private
def user
@user = current_user
......
......@@ -5,7 +5,10 @@ class TeamMembersController < ApplicationController
# Authorize
before_filter :add_project_abilities
before_filter :authorize_read_project!
before_filter :authorize_admin_project!, except: [:show]
before_filter :authorize_admin_project!, except: [:index, :show]
def index
end
def show
@team_member = project.users_projects.find(params[:id])
......@@ -22,7 +25,7 @@ class TeamMembersController < ApplicationController
params[:project_access]
)
redirect_to team_project_path(@project)
redirect_to project_team_index_path(@project)
end
def update
......@@ -32,7 +35,7 @@ class TeamMembersController < ApplicationController
unless @team_member.valid?
flash[:alert] = "User should have at least one role"
end
redirect_to team_project_path(@project)
redirect_to project_team_index_path(@project)
end
def destroy
......@@ -40,7 +43,7 @@ class TeamMembersController < ApplicationController
@team_member.destroy
respond_to do |format|
format.html { redirect_to team_project_path(@project) }
format.html { redirect_to project_team_index_path(@project) }
format.js { render nothing: true }
end
end
......
......@@ -16,7 +16,7 @@ class CommitDecorator < ApplicationDecorator
# In case this first line is longer than 80 characters, it is cut off
# after 70 characters and ellipses (`&hellp;`) are appended.
def title
return no_commit_message unless safe_message
return no_commit_message if safe_message.blank?
title_end = safe_message.index(/\n/)
if (!title_end && safe_message.length > 80) || (title_end && title_end > 80)
......
......@@ -62,7 +62,7 @@ module ApplicationHelper
{ label: "#{@project.name} / Wall", url: wall_project_path(@project) },
{ label: "#{@project.name} / Tree", url: tree_project_ref_path(@project, @project.root_ref) },
{ label: "#{@project.name} / Commits", url: project_commits_path(@project) },
{ label: "#{@project.name} / Team", url: team_project_path(@project) }
{ label: "#{@project.name} / Team", url: project_team_index_path(@project) }
]
end
......@@ -104,7 +104,8 @@ module ApplicationHelper
# Profile Area
when :profile; current_page?(controller: "profile", action: :show)
when :password; current_page?(controller: "profile", action: :password)
when :history; current_page?(controller: "profile", action: :history)
when :account; current_page?(controller: "profile", action: :account)
when :token; current_page?(controller: "profile", action: :token)
when :design; current_page?(controller: "profile", action: :design)
when :ssh_keys; controller.controller_name == "keys"
......@@ -135,4 +136,10 @@ module ApplicationHelper
"Never"
end
end
def authbutton(provider, size = 64)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag("authbuttons/#{file_name}",
alt: "Sign in with #{provider.to_s.titleize}")
end
end
......@@ -11,7 +11,9 @@ module GitlabMarkdownHelper
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {})
gfm_body = gfm(body, html_options)
return "" if body.blank?
gfm_body = gfm(escape_once(body), html_options)
gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
"</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".length +1
......
module NotesHelper
def loading_more_notes?
params[:loading_more].present?
end
def loading_new_notes?
params[:loading_new].present?
end
def note_vote_class(note)
if note.upvote?
"vote upvote"
elsif note.downvote?
"vote downvote"
end
end
end
module ProfileHelper
def oauth_active_class provider
if current_user.provider == provider.to_s
'active'
end
end
end
......@@ -2,5 +2,9 @@ module ProjectsHelper
def grouper_project_members(project)
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end
def remove_from_team_message(project, member)
"You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
end
end
......@@ -8,7 +8,7 @@ module TabHelper
end
def project_tab_class
[:show, :files, :team, :edit, :update].each do |action|
[:show, :files, :edit, :update].each do |action|
return "current" if current_page?(controller: "projects", action: action, id: @project)
end
......
......@@ -18,7 +18,8 @@ module TreeHelper
end
def tree_full_path(content)
if params[:path]
content.name.force_encoding('utf-8')
if params[:path]
File.join(params[:path], content.name)
else
content.name
......
......@@ -35,13 +35,21 @@ class Event < ActiveRecord::Base
end
# Next events currently enabled for system
# - push
# - push
# - new issue
# - merge request
def allowed?
push? || issue? || merge_request? || membership_changed?
end
def project_name
if project
project.name
else
"(deleted)"
end
end
def push?
action == self.class::Pushed && valid_push?
end
......@@ -58,31 +66,31 @@ class Event < ActiveRecord::Base
action == self.class::Reopened
end
def issue?
def issue?
target_type == "Issue"
end
def merge_request?
def merge_request?
target_type == "MergeRequest"
end
def new_issue?
target_type == "Issue" &&
def new_issue?
target_type == "Issue" &&
action == Created
end
def new_merge_request?
target_type == "MergeRequest" &&
def new_merge_request?
target_type == "MergeRequest" &&
action == Created
end
def changed_merge_request?
target_type == "MergeRequest" &&
def changed_merge_request?
target_type == "MergeRequest" &&
[Closed, Reopened].include?(action)
end
def changed_issue?
target_type == "Issue" &&
def changed_issue?
target_type == "Issue" &&
[Closed, Reopened].include?(action)
end
......@@ -98,7 +106,7 @@ class Event < ActiveRecord::Base
joined? || left?
end
def issue
def issue
target if target_type == "Issue"
end
......@@ -106,7 +114,7 @@ class Event < ActiveRecord::Base
target if target_type == "MergeRequest"
end
def author
def author
@author ||= User.find(author_id)
end
......@@ -119,7 +127,7 @@ class Event < ActiveRecord::Base
'joined'
elsif left?
'left'
else
else
"opened"
end
end
......
class Issue < ActiveRecord::Base
include IssueCommonality
include Upvote
include Votes
acts_as_taggable_on :labels
......
......@@ -2,7 +2,7 @@ require File.join(Rails.root, "app/models/commit")
class MergeRequest < ActiveRecord::Base
include IssueCommonality
include Upvote
include Votes
BROKEN_DIFF = "--broken-diff"
......
......@@ -36,7 +36,7 @@ class Note < ActiveRecord::Base
scope :today, where("created_at >= :date", date: Date.today)
scope :last_week, where("created_at >= :date", date: (Date.today - 7.days))
scope :since, lambda { |day| where("created_at >= :date", date: (day)) }
scope :fresh, order("created_at DESC")
scope :fresh, order("created_at ASC, id ASC")
scope :inc_author_project, includes(:project, :author)
scope :inc_author, includes(:author)
......@@ -105,6 +105,12 @@ class Note < ActiveRecord::Base
def upvote?
note.start_with?('+1') || note.start_with?(':+1:')
end
# Returns true if this is a downvote note,
# otherwise false is returned
def downvote?
note.start_with?('-1') || note.start_with?(':-1:')
end
end
# == Schema Information
#
......
......@@ -171,6 +171,10 @@ class Project < ActiveRecord::Base
end
end
def wiki_notes
Note.where(noteable_id: wikis.map(&:id), noteable_type: 'Wiki', project_id: self.id)
end
def project_id
self.id
end
......
......@@ -16,7 +16,7 @@ class Tree
def initialize(raw_tree, project, ref = nil, path = nil)
@project, @ref, @path = project, ref, path,
@tree = if path
raw_tree / path
raw_tree / path.dup.force_encoding('ascii-8bit')
else
raw_tree
end
......
......@@ -86,33 +86,20 @@ class User < ActiveRecord::Base
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
end
def self.find_for_ldap_auth(auth, signed_in_resource=nil)
uid = auth.info.uid
provider = auth.provider
name = auth.info.name.force_encoding("utf-8")
email = auth.info.email.downcase unless auth.info.email.nil?
raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil?
if @user = User.find_by_extern_uid_and_provider(uid, provider)
@user
# workaround for backward compatibility
elsif @user = User.find_by_email(email)
logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}"
@user.update_attributes(:extern_uid => uid, :provider => provider)
@user
else
logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}"
password = Devise.friendly_token[0, 8].downcase
@user = User.create(
:extern_uid => uid,
:provider => provider,
:name => name,
:email => email,
:password => password,
:password_confirmation => password,
:projects_limit => Gitlab.config.default_projects_limit
)
end
def self.create_from_omniauth(auth, ldap = false)
gitlab_auth.create_from_omniauth(auth, ldap)
end
def self.find_or_new_for_omniauth(auth)
gitlab_auth.find_or_new_for_omniauth(auth)
end
def self.find_for_ldap_auth(auth, signed_in_resource = nil)
gitlab_auth.find_for_ldap_auth(auth, signed_in_resource)
end
def self.gitlab_auth
Gitlab::Auth.new
end
def self.search query
......@@ -148,4 +135,3 @@ end
# bio :string(255)
# blocked :boolean(1) default(FALSE), not null
#
......@@ -28,7 +28,6 @@ class Wiki < ActiveRecord::Base
end
new_wiki
end
end
end
# == Schema Information
......
......@@ -4,6 +4,18 @@ class ProjectObserver < ActiveRecord::Observer
end
def after_destroy(project)
log_info("Project \"#{project.name}\" was removed")
project.destroy_repository
end
def after_create project
log_info("#{project.owner.name} created a new project \"#{project.name}\"")
end
protected
def log_info message
Gitlab::AppLogger.info message
end
end
class UserObserver < ActiveRecord::Observer
def after_create(user)
log_info("User \"#{user.name}\" (#{user.email}) was created")
Notify.new_user_email(user.id, user.password).deliver
end
def after_destroy user
log_info("User \"#{user.name}\" (#{user.email}) was removed")
end
protected
def log_info message
Gitlab::AppLogger.info message
end
end
......@@ -14,8 +14,8 @@ class UsersProjectObserver < ActiveRecord::Observer
def after_destroy(users_project)
Event.create(
project_id: users_project.project.id,
action: Event::Left,
project_id: users_project.project.id,
action: Event::Left,
author_id: users_project.user.id
)
end
......
module Upvote
# Return the number of +1 comments (upvotes)
def upvotes
notes.select(&:upvote?).size
end
end
module Votes
# Return the number of +1 comments (upvotes)
def upvotes
notes.select(&:upvote?).size
end
def upvotes_in_percent
if votes_count.zero?
0
else
100.0 / votes_count * upvotes
end
end
# Return the number of -1 comments (downvotes)
def downvotes
notes.select(&:downvote?).size
end
def downvotes_in_percent
if votes_count.zero?
0
else
100.0 - upvotes_in_percent
end
end
# Return the total number of votes
def votes_count
upvotes + downvotes
end
end
.file_holder#README
.file_title
%i.icon-file
githost.log
.file_content.logs
%ol
- Gitlab::Logger.read_latest.each do |line|
%li
%p= line
%ul.nav.nav-tabs.log-tabs
%li.active
= link_to "githost.log", "#githost", 'data-toggle' => 'tab'
%li
= link_to "application.log", "#application", 'data-toggle' => 'tab'
.tab-content
.tab-pane.active#githost
.file_holder#README
.file_title
%i.icon-file
githost.log
.file_content.logs
%ol
- Gitlab::GitLogger.read_latest.each do |line|
%li
%p= line
.tab-pane#application
.file_holder#README
.file_title
%i.icon-file
application.log
.file_content.logs
%ol
- Gitlab::AppLogger.read_latest.each do |line|
%li
%p= line
......@@ -32,7 +32,7 @@
- unless project.new_record?
.clearfix
= f.label :owner_id
.input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }
.input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
- if project.repo_exists?
.clearfix
......@@ -69,7 +69,6 @@
:javascript
$(function(){
$('#project_owner_id').chosen();
new Projects();
})
......@@ -71,25 +71,11 @@
%th Project Access:
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
%tr
%td= submit_tag 'Add', class: "btn primary"
%td
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
:css
form select {
width:150px;
}
#user_ids {
width:300px;
}
:javascript
$('select#user_ids').chosen();
$('select#repo_access').chosen();
$('select#project_access').chosen();
%h3 Resque
%iframe{src: resque_url, width: 1168, height: 600, style: "border: none"}
%h3.page_title Resque
%br
.ui-box
%iframe{src: resque_url, width: '100%', height: 600, style: "border: none"}
......@@ -8,20 +8,9 @@
.clearfix
%label Project Access:
.input
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select"
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
%br
.actions
= f.submit 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
:css
form select {
width:300px;
}
:javascript
$('select#team_member_user_id').chosen();
$('select#team_member_project_id').chosen();
$('select#team_member_repo_access').chosen();
$('select#team_member_project_access').chosen();
......@@ -68,8 +68,8 @@
%th Project Access:
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
%tr
%td= submit_tag 'Add', class: "btn primary"
......@@ -97,17 +97,3 @@
%td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
:css
form select {
width:150px;
}
#project_ids {
width:300px;
}
:javascript
$('select#project_ids').chosen();
$('select#repo_access').chosen();
$('select#project_access').chosen();
......@@ -11,10 +11,10 @@
= link_to tree_project_ref_path(@project, @commit.id), class: "browse-button primary grouped" do
%strong Browse Code »
%h3.commit-title.page_title
= gfm @commit.title
= gfm escape_once(@commit.title)
- if @commit.description.present?
%pre.commit-description
= gfm @commit.description
= gfm escape_once(@commit.description)
.commit-info
.row
.span4
......
%ul.nav.nav-tabs
%li
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
= hidden_field_tag :destination, "commits"
%li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'}
%li{class: "#{'active' if current_page?(project_commits_path(@project)) }"}
= link_to project_commits_path(@project) do
Commits
......@@ -20,14 +16,8 @@
Tags
%span.badge= @project.repo.tag_count
- if current_page?(project_commits_path(@project)) && current_user.private_token
%li.right
%span.rss-icon
= link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do
= image_tag "rss_ui.png", title: "feed"
:javascript
$(function(){
$('.project-refs-select').chosen();
});
......@@ -13,14 +13,11 @@
%td.old_line
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- if @comments_allowed
= link_to "", "#", class: "line_note_link", "line_code" => line_code, title: "Add note for this line"
= render "notes/per_line_note_link", line_code: line_code
%td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
%td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} &nbsp;"
- if @comments_allowed
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at).reverse
- comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
- unless comments.empty?
- comments.each_with_index do |note, i|
= render "notes/reply_button", line_code: line_code if i.zero?
= render "notes/per_line_show", note: note
- @line_notes.reject!{ |n| n == note }
= render "notes/per_line_notes_with_reply", notes: comments
= render "commits/commit_box"
= render "commits/diffs", diffs: @commit.diffs
= render "notes/notes", tid: @commit.id, tt: "commit"
= render "notes/notes_with_form", tid: @commit.id, tt: "commit"
= render "notes/per_line_form"
......
......@@ -31,13 +31,19 @@
%span= project_last_activity(project)
.bottom= paginate @projects, theme: "gitlab"
%hr
%div
%span.rss-icon
= link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
= image_tag "rss_ui.png", title: "feed"
%strong News Feed
%hr
.gitlab-promo
= link_to "Homepage", "http://gitlabhq.com"
= link_to "Blog", "http://blog.gitlabhq.com"
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
- else
%h3.nothing_here_message There are no projects you have access to.
%br
......
......@@ -15,7 +15,7 @@
$(function() {
$('#new_user').toggle();
});
= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
= f.text_field :email, :class => "text top", :placeholder => "Email"
= f.password_field :password, :class => "text bottom", :placeholder => "Password"
- if devise_mapping.rememberable?
......
......@@ -15,7 +15,7 @@
.right
= render :partial => "devise/shared/links"
- if devise_mapping.omniauthable?
%hr/
- resource_class.omniauth_providers.each do |provider|
%hr/
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary"
%br/
%span
= link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
......@@ -5,4 +5,4 @@
%strong.cdark= commit.author_name
&ndash;
= image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16
= gfm truncate(commit.title, length: 50) rescue "--broken encoding"
= gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding"
......@@ -2,7 +2,7 @@
.event_lp
%div
= image_tag gravatar_icon(event.author_email), class: "avatar"
%span Your pushed to
%span Your pushed to
= event.ref_type
= link_to project_commits_path(event.project, ref: event.ref_name) do
%strong= truncate(event.ref_name, length: 28)
......
......@@ -2,7 +2,7 @@
%strong #{event.author_name}
%span.event_label{class: event.action_name}= event.action_name
project
%strong= link_to event.project.name, event.project
%strong= link_to event.project_name, event.project
%span.cgray
= time_ago_in_words(event.created_at)
ago.
......
......@@ -18,12 +18,12 @@
= f.label :assignee_id do
%i.icon-user
Assign to
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" })
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
.issue_milestone
= f.label :milestone_id do
%i.icon-time
Milestone
.input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" })
.input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
.issue_description
.clearfix
......
......@@ -4,7 +4,7 @@
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.right
- issue.labels.each do |label|
%span.label.label-issue.grouped
%span.label.label-tag.grouped
%i.icon-tag
= label.name
- if issue.notes.any?
......@@ -34,5 +34,5 @@
- else
&nbsp;
- if issue.upvotes > 0
%span.badge.badge-success= "+#{issue.upvotes}"
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
});
......@@ -12,7 +12,7 @@
= form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
= hidden_field_tag :project_id, @project.id, { id: 'project_id' }
= hidden_field_tag :status, params[:f]
= search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib' }
= search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib search-text-input' }
.clearfix
......
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
$('select#issue_milestone_id').chosen();
});
......@@ -8,22 +8,22 @@
%span.right
- if can?(current_user, :admin_project, @project) || @issue.author == current_user
- if @issue.closed
= link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small"
= link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped success"
- else
= link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small", title: "Close Issue"
= link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close Issue"
- if can?(current_user, :admin_project, @project) || @issue.author == current_user
= link_to edit_project_issue_path(@project, @issue), class: "btn small" do
= link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
%i.icon-edit
Edit
%br
- if @issue.upvotes > 0
.upvotes#upvotes= "+#{pluralize @issue.upvotes, 'upvote'}"
.right
.span3#votes= render 'votes/votes_block', votable: @issue
.back_link
= link_to project_issues_path(@project) do
&larr; To issues list
.main_box
.top_box_content
%h4
......@@ -31,7 +31,7 @@
.alert-message.error.status_info Closed
- else
.alert-message.success.status_info Open
= gfm @issue.title
= gfm escape_once(@issue.title)
.middle_box_content
%cite.cgray Created by
......@@ -61,4 +61,4 @@
= markdown @issue.description
.issue_notes#notes= render "notes/notes", tid: @issue.id, tt: "issue"
.issue_notes.voting_notes#notes= render "notes/notes_with_form", tid: @issue.id, tt: "issue"
%li.wll
%strong= label.name
%strong
%i.icon-tag
= label.name
.right
%span= pluralize label.count, 'issue'
= link_to project_issues_path(label_name: label.name) do
%strong
= pluralize(label.count, 'issue')
= "»"
......@@ -9,20 +9,20 @@
%li.home{class: tab_class(:profile)}
= link_to "Profile", profile_path
%li{class: tab_class(:password)}
= link_to "Password", profile_password_path
%li{class: tab_class(:account)}
= link_to "Account", profile_account_path
%li{class: tab_class(:ssh_keys)}
= link_to keys_path do
SSH Keys
%span.count= current_user.keys.count
%li{class: tab_class(:token)}
= link_to "Token", profile_token_path
%li{class: tab_class(:design)}
= link_to "Design", profile_design_path
%li{class: tab_class(:history)}
= link_to "History", profile_history_path
.content
= yield
......@@ -16,7 +16,7 @@
.padded
= f.label :source_branch, "From", class: "control-label"
.controls
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
= f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
.mr_source_commit
.span2
......@@ -28,7 +28,7 @@
.padded
= f.label :target_branch, "To", class: "control-label"
.controls
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
= f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
.mr_target_commit
%h4.cdark 2. Fill info
......@@ -43,7 +43,7 @@
= f.label :assignee_id do
%i.icon-user
Assign to
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px")
.input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
.control-group
......@@ -56,18 +56,12 @@
= link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
Cancel
:javascript
$(function(){
disableButtonIfEmptyField("#merge_request_title", ".save-btn");
$('select#merge_request_assignee_id').chosen();
$('select#merge_request_source_branch').chosen();
$('select#merge_request_target_branch').chosen();
var source_branch = $("#merge_request_source_branch");
var target_branch = $("#merge_request_target_branch");
$.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
$.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
......@@ -79,4 +73,3 @@
$.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
});
});
......@@ -23,5 +23,6 @@
authored by #{merge_request.author_name}
= time_ago_in_words(merge_request.created_at)
ago
- if merge_request.upvotes > 0
%span.badge.badge-success= "+#{merge_request.upvotes}"
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
......@@ -15,8 +15,8 @@
%i.icon-list-alt
Diff
.merge_request_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
= render("notes/notes", tid: @merge_request.id, tt: "merge_request")
.merge_request_notes.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
= render("notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")
.merge-request-diffs
= render "merge_requests/show/diffs" if @diffs
.status
......
= render "show"
:javascript
$(function(){
PerLineNotes.init();
});
:plain
$(".merge-request-diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}");
$(function(){
PerLineNotes.init();
});
:plain
$(".merge-request-notes").html("#{escape_javascript(render("notes/notes", tid: @merge_request.id, tt: "merge_request"))}");
$(".merge-request-notes").html("#{escape_javascript(render notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")}");
......@@ -5,7 +5,7 @@
.alert-message.error.status_info Closed
- else
.alert-message.success.status_info Open
= gfm @merge_request.title
= gfm escape_once(@merge_request.title)
.middle_box_content
%div
......
......@@ -23,10 +23,8 @@
%i.icon-edit
Edit
%br
- if @merge_request.upvotes > 0
.upvotes#upvotes= "+#{pluralize @merge_request.upvotes, 'upvote'}"
.right
.span3#votes= render 'votes/votes_block', votable: @merge_request
.back_link
= link_to project_merge_requests_path(@project) do
......
= render "form"
:javascript
$(function(){
$('select#issue_assignee_id').chosen();
});
......@@ -21,7 +21,7 @@
.alert-message.error.status_info Closed
- else
.alert-message.success.status_info Open
= gfm @milestone.title
= gfm escape_once(@milestone.title)
%small.right= @milestone.expires_at
.middle_box_content
......
......@@ -14,7 +14,7 @@
.right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
.clearfix
.row.note_advanced_opts.hide
.row.note_advanced_opts
.span3
= f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
= link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
......
......@@ -5,8 +5,9 @@
$('.note-form-holder #preview-link').text('Preview');
$('.note-form-holder #preview-note').hide();
$('.note-form-holder').show();
NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}");
NoteList.appendNewNote(#{note.id}, "#{escape_javascript(render "notes/note", note: note)}");
- else
:plain
$(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
$(".note-form-holder").replaceWith("#{escape_javascript(render 'form')}");
- if note.valid?
:plain
$(".per_line_form").hide();
$('.line-note-form-holder textarea').val("");
$("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove();
var trEl = $(".#{note.line_code}").parent();
trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}");
trEl.after("#{escape_javascript(render partial: "notes/reply_button", locals: {line_code: note.line_code})}");
- if note.valid?
:plain
// hide and reset the form
$(".per_line_form").hide();
$('.line-note-form-holder textarea').val("");
// find the reply button for this line
// (might not be there if this is the first note)
var trRpl = $("a.line_note_reply_link[data-line-code='#{note.line_code}']").closest("tr");
if (trRpl.size() == 0) {
// find the commented line ...
var trEl = $(".#{note.line_code}").parent();
// ... and insert the note and the reply button after it
trEl.after("#{escape_javascript(render "notes/per_line_reply_button", line_code: note.line_code)}");
trEl.after("#{escape_javascript(render "notes/per_line_note", note: note)}");
} else {
// instert new note before reply button
trRpl.before("#{escape_javascript(render "notes/per_line_note", note: note)}");
}
- unless @notes.blank?
- if params[:last_id]
:plain
NoteList.replace("#{escape_javascript(render(partial: 'notes/notes_list'))}");
- elsif params[:first_id]
:plain
NoteList.append(#{@notes.last.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}");
- else
:plain
NoteList.setContent(#{@notes.last.id}, #{@notes.first.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}");
- else
- if params[:first_id]
:plain
NoteList.append(#{params[:first_id]}, "");
%li{id: dom_id(note), class: "note"}
%li{id: dom_id(note), class: "note #{note_vote_class(note)}"}
= image_tag gravatar_icon(note.author.email), class: "avatar s32"
%div.note-author
%strong= note.author_name
......@@ -6,8 +6,16 @@
%cite.cgray
= time_ago_in_words(note.updated_at)
ago
- if note.upvote?
%span.label.label-success
%i.icon-thumbs-up
\+1
- if note.downvote?
%span.label.label-error
%i.icon-thumbs-down
\-1
- if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
= link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do
= link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do
%i.icon-trash
Remove
......
- if can? current_user, :write_note, @project
= render "notes/form"
.clear
%hr
%ul#new_notes_list
%ul#notes-list
.status
- @notes.each do |note|
- next unless note.author
= render "note", note: note
:javascript
$(function(){
NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}");
});
- @notes.each do |note|
- next unless note.author
= render partial: "notes/show", locals: {note: note}
%ul#notes-list
%ul#new-notes-list
.notes-status
- if can? current_user, :write_note, @project
= render "notes/common_form"
:javascript
$(function(){
NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}");
});
%tr.line_notes_row
%td{colspan: 3}
%ul
= render partial: "notes/show", locals: {note: note}
= render "notes/note", note: note
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment