Commit e6ce47291b3f08ebe18c2450fc4f21a2a3a2b8a9

Authored by Alex Denisov
2 parents 77bfc591 61049424

master merged

Showing 288 changed files with 4642 additions and 2548 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 288 files displayed.

CONTRIBUTING.md 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +## Contribute to GitLab
  2 +
  3 +If you want to contribute to GitLab, follow this process:
  4 +
  5 +1. Fork the project
  6 +2. Create a feature branch
  7 +3. Code
  8 +4. Create a pull request
  9 +
  10 +We only accept pull requests if:
  11 +
  12 +* Your code has proper tests and all tests pass
  13 +* Your code can be merged w/o problems
  14 +* It wont broke existing functionality
  15 +* Its a quality code
  16 +* We like it :)
  17 +
  18 +## [You may need a developer VM](https://github.com/gitlabhq/developer-vm)
  19 +
  20 +## Running tests
  21 +
  22 +To run the specs for GitLab, you need to run seeds for test db.
  23 +
  24 + cd gitlabhq
  25 + rake db:seed_fu RAILS_ENV=test
  26 +
  27 +Then you can run the test suite with rake:
  28 +
  29 + rake gitlab:test
  30 +
... ...
Gemfile
1 1 source "http://rubygems.org"
2 2  
  3 +def darwin_only(require_as)
  4 + RUBY_PLATFORM.include?('darwin') && require_as
  5 +end
  6 +
  7 +def linux_only(require_as)
  8 + RUBY_PLATFORM.include?('linux') && require_as
  9 +end
  10 +
3 11 gem "rails", "3.2.8"
4 12  
5 13 # Supported DBs
... ... @@ -8,6 +16,10 @@ gem "mysql2"
8 16  
9 17 # Auth
10 18 gem "devise", "~> 2.1.0"
  19 +gem 'omniauth'
  20 +gem 'omniauth-google-oauth2'
  21 +gem 'omniauth-twitter'
  22 +gem 'omniauth-github'
11 23  
12 24 # GITLAB patched libs
13 25 gem "grit", :git => "https://github.com/gitlabhq/grit.git", :ref => "7f35cb98ff17d534a07e3ce6ec3d580f67402837"
... ... @@ -98,21 +110,28 @@ group :development do
98 110 end
99 111  
100 112 group :development, :test do
  113 + gem 'spinach-rails'
101 114 gem "rspec-rails"
102 115 gem "capybara"
103 116 gem "capybara-webkit"
104 117 gem "headless"
105   - gem "autotest"
106   - gem "autotest-rails"
107 118 gem "pry"
108 119 gem "awesome_print"
109 120 gem "database_cleaner"
110 121 gem "launchy"
111 122 gem 'factory_girl_rails'
  123 +
  124 + # Guard
  125 + gem 'guard-rspec'
  126 + gem 'guard-spinach'
  127 +
  128 + # Notification
  129 + gem 'rb-fsevent', :require => darwin_only('rb-fsevent')
  130 + gem 'growl', :require => darwin_only('growl')
  131 + gem 'rb-inotify', :require => linux_only('rb-inotify')
112 132 end
113 133  
114 134 group :test do
115   - gem 'cucumber-rails', :require => false
116 135 gem "simplecov", :require => false
117 136 gem "shoulda-matchers"
118 137 gem 'email_spec'
... ...
Gemfile.lock
... ... @@ -68,7 +68,6 @@ GIT
68 68 GEM
69 69 remote: http://rubygems.org/
70 70 specs:
71   - ZenTest (4.8.1)
72 71 actionmailer (3.2.8)
73 72 actionpack (= 3.2.8)
74 73 mail (~> 2.4.4)
... ... @@ -100,10 +99,6 @@ GEM
100 99 rails (~> 3.0)
101 100 addressable (2.2.8)
102 101 arel (3.0.2)
103   - autotest (4.4.6)
104   - ZenTest (>= 4.4.1)
105   - autotest-rails (4.1.2)
106   - ZenTest (~> 4.5)
107 102 awesome_print (1.0.2)
108 103 bcrypt-ruby (3.0.1)
109 104 blankslate (2.1.2.4)
... ... @@ -137,16 +132,8 @@ GEM
137 132 execjs
138 133 coffee-script-source (1.3.3)
139 134 colored (1.2)
  135 + colorize (0.5.8)
140 136 crack (0.3.1)
141   - cucumber (1.2.1)
142   - builder (>= 2.1.2)
143   - diff-lcs (>= 1.1.3)
144   - gherkin (~> 2.11.0)
145   - json (>= 1.4.6)
146   - cucumber-rails (1.3.0)
147   - capybara (>= 1.1.2)
148   - cucumber (>= 1.1.8)
149   - nokogiri (>= 1.5.0)
150 137 daemons (1.1.8)
151 138 database_cleaner (0.8.0)
152 139 devise (2.1.2)
... ... @@ -171,12 +158,13 @@ GEM
171 158 factory_girl_rails (4.0.0)
172 159 factory_girl (~> 4.0.0)
173 160 railties (>= 3.0.0)
  161 + faraday (0.8.4)
  162 + multipart-post (~> 1.1)
174 163 ffaker (1.14.0)
175 164 ffi (1.0.11)
176 165 foreman (0.47.0)
177 166 thor (>= 0.13.6)
178   - gherkin (2.11.0)
179   - json (>= 1.4.6)
  167 + gherkin-ruby (0.2.1)
180 168 git (1.2.5)
181 169 github-markup (0.7.4)
182 170 gitlab_meta (2.9)
... ... @@ -186,6 +174,15 @@ GEM
186 174 multi_xml
187 175 rack
188 176 rack-mount
  177 + growl (1.0.3)
  178 + guard (1.3.2)
  179 + listen (>= 0.4.2)
  180 + thor (>= 0.14.6)
  181 + guard-rspec (1.2.1)
  182 + guard (>= 1.1)
  183 + guard-spinach (0.0.2)
  184 + guard (>= 1.1)
  185 + spinach
189 186 haml (3.1.6)
190 187 haml-rails (0.3.4)
191 188 actionpack (~> 3.0)
... ... @@ -199,6 +196,7 @@ GEM
199 196 httparty (0.8.3)
200 197 multi_json (~> 1.0)
201 198 multi_xml
  199 + httpauth (0.1)
202 200 i18n (0.6.1)
203 201 journey (1.0.4)
204 202 jquery-rails (2.0.2)
... ... @@ -208,6 +206,8 @@ GEM
208 206 jquery-rails
209 207 railties (>= 3.1.0)
210 208 json (1.7.5)
  209 + jwt (0.1.5)
  210 + multi_json (>= 1.0)
211 211 kaminari (0.14.0)
212 212 actionpack (>= 3.0.0)
213 213 activesupport (>= 3.0.0)
... ... @@ -219,6 +219,7 @@ GEM
219 219 libv8 (3.3.10.4)
220 220 libwebsocket (0.1.3)
221 221 addressable
  222 + listen (0.5.0)
222 223 mail (2.4.4)
223 224 i18n (>= 0.4.0)
224 225 mime-types (~> 1.16)
... ... @@ -229,12 +230,35 @@ GEM
229 230 sprockets (~> 2.0)
230 231 multi_json (1.3.6)
231 232 multi_xml (0.5.1)
  233 + multipart-post (1.1.5)
232 234 mysql2 (0.3.11)
233 235 net-ldap (0.2.2)
234 236 nokogiri (1.5.3)
  237 + oauth (0.4.7)
  238 + oauth2 (0.8.0)
  239 + faraday (~> 0.8)
  240 + httpauth (~> 0.1)
  241 + jwt (~> 0.1.4)
  242 + multi_json (~> 1.0)
  243 + rack (~> 1.2)
235 244 omniauth (1.1.0)
236 245 hashie (~> 1.2)
237 246 rack
  247 + omniauth-github (1.0.3)
  248 + omniauth (~> 1.0)
  249 + omniauth-oauth2 (~> 1.1)
  250 + omniauth-google-oauth2 (0.1.13)
  251 + omniauth (~> 1.0)
  252 + omniauth-oauth2
  253 + omniauth-oauth (1.0.1)
  254 + oauth
  255 + omniauth (~> 1.0)
  256 + omniauth-oauth2 (1.1.0)
  257 + oauth2 (~> 0.8.0)
  258 + omniauth (~> 1.0)
  259 + omniauth-twitter (0.0.13)
  260 + multi_json (~> 1.3)
  261 + omniauth-oauth (~> 1.0)
238 262 orm_adapter (0.3.0)
239 263 polyglot (0.3.3)
240 264 posix-spawn (0.3.6)
... ... @@ -274,6 +298,9 @@ GEM
274 298 raindrops (0.9.0)
275 299 rake (0.9.2.2)
276 300 raphael-rails (1.5.2)
  301 + rb-fsevent (0.9.1)
  302 + rb-inotify (0.8.8)
  303 + ffi (>= 0.5.0)
277 304 rdoc (3.12)
278 305 json (~> 1.4)
279 306 redcarpet (2.1.1)
... ... @@ -336,6 +363,13 @@ GEM
336 363 tilt (~> 1.3, >= 1.3.3)
337 364 six (0.2.0)
338 365 slop (2.4.4)
  366 + spinach (0.5.2)
  367 + colorize
  368 + gherkin-ruby (~> 0.2.0)
  369 + spinach-rails (0.1.8)
  370 + capybara (~> 1)
  371 + railties (>= 3)
  372 + spinach (>= 0.4)
339 373 sprockets (2.1.3)
340 374 hike (~> 1.2)
341 375 rack (~> 1.0)
... ... @@ -378,8 +412,6 @@ PLATFORMS
378 412 DEPENDENCIES
379 413 acts-as-taggable-on (= 2.3.1)
380 414 annotate!
381   - autotest
382   - autotest-rails
383 415 awesome_print
384 416 bootstrap-sass (= 2.0.4)
385 417 capybara
... ... @@ -389,7 +421,6 @@ DEPENDENCIES
389 421 chosen-rails
390 422 coffee-rails (= 3.2.2)
391 423 colored
392   - cucumber-rails
393 424 database_cleaner
394 425 devise (~> 2.1.0)
395 426 draper
... ... @@ -404,6 +435,9 @@ DEPENDENCIES
404 435 grack!
405 436 grape (~> 0.2.1)
406 437 grit!
  438 + growl
  439 + guard-rspec
  440 + guard-spinach
407 441 haml-rails
408 442 headless
409 443 httparty
... ... @@ -415,12 +449,18 @@ DEPENDENCIES
415 449 linguist (~> 1.0.0)!
416 450 modernizr (= 2.5.3)
417 451 mysql2
  452 + omniauth
  453 + omniauth-github
  454 + omniauth-google-oauth2
418 455 omniauth-ldap!
  456 + omniauth-twitter
419 457 pry
420 458 pygments.rb!
421 459 rack-mini-profiler
422 460 rails (= 3.2.8)
423 461 raphael-rails (= 1.5.2)
  462 + rb-fsevent
  463 + rb-inotify
424 464 redcarpet (~> 2.1.1)
425 465 resque (~> 1.20.0)
426 466 resque_mailer
... ... @@ -432,6 +472,7 @@ DEPENDENCIES
432 472 shoulda-matchers
433 473 simplecov
434 474 six
  475 + spinach-rails
435 476 sqlite3
436 477 stamp
437 478 test_after_commit
... ...
Guardfile 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +# A sample Guardfile
  2 +# More info at https://github.com/guard/guard#readme
  3 +
  4 +guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
  5 + watch(%r{^spec/.+_spec\.rb$})
  6 + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
  7 + watch('spec/spec_helper.rb') { "spec" }
  8 +
  9 + # Rails example
  10 + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
  11 + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
  12 + 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"] }
  13 + watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
  14 + watch('config/routes.rb') { "spec/routing" }
  15 + watch('app/controllers/application_controller.rb') { "spec/controllers" }
  16 +
  17 + # Capybara request specs
  18 + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
  19 +end
  20 +
  21 +guard 'spinach' do
  22 + watch(%r|^features/(.*)\.feature|)
  23 + watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
  24 + "features/#{m[1]}#{m[2]}.feature"
  25 + end
  26 +end
... ...
app/assets/javascripts/admin.js.coffee
... ... @@ -6,3 +6,7 @@ $ ->
6 6 elems.val('').attr 'disabled', true
7 7 else
8 8 elems.removeAttr 'disabled'
  9 +
  10 + $('.log-tabs a').click (e) ->
  11 + e.preventDefault()
  12 + $(this).tab('show')
... ...
app/assets/javascripts/application.js
... ... @@ -11,7 +11,7 @@
11 11 //= require jquery.endless-scroll
12 12 //= require jquery.highlight
13 13 //= require jquery.waitforimages
14   -//= require bootstrap-modal
  14 +//= require bootstrap
15 15 //= require modernizr
16 16 //= require chosen-jquery
17 17 //= require raphael
... ...
app/assets/javascripts/main.js.coffee
... ... @@ -24,6 +24,9 @@ $ ->
24 24 # Click a .one_click_select field, select the contents
25 25 $(".one_click_select").live 'click', -> $(this).select()
26 26  
  27 + # Initialize chosen selects
  28 + $('select.chosen').chosen()
  29 +
27 30 # Disable form buttons while a form is submitting
28 31 $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
29 32 buttons = $('[type="submit"]', this)
... ...
app/assets/javascripts/note.js
... ... @@ -1,182 +0,0 @@
1   -var NoteList = {
2   -
3   - notes_path: null,
4   - target_params: null,
5   - target_id: 0,
6   - target_type: null,
7   - first_id: 0,
8   - last_id: 0,
9   - disable:false,
10   -
11   - init:
12   - function(tid, tt, path) {
13   - this.notes_path = path + ".js";
14   - this.target_id = tid;
15   - this.target_type = tt;
16   - this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
17   -
18   - // get notes
19   - this.getContent();
20   -
21   - // get new notes every n seconds
22   - this.initRefresh();
23   -
24   - $('.delete-note').live('ajax:success', function() {
25   - $(this).closest('li').fadeOut(); });
26   -
27   - $(".note-form-holder").live("ajax:before", function(){
28   - $(".submit_note").disable()
29   - })
30   -
31   - $(".note-form-holder").live("ajax:complete", function(){
32   - $(".submit_note").enable()
33   - })
34   -
35   - disableButtonIfEmptyField(".note-text", ".submit_note");
36   -
37   - $(".note-text").live("focus", function(){
38   - $(this).css("height", "80px");
39   - $('.note_advanced_opts').show();
40   - });
41   -
42   - $("#note_attachment").change(function(e){
43   - var val = $('.input-file').val();
44   - var filename = val.replace(/^.*[\\\/]/, '');
45   - $(".file_name").text(filename);
46   - });
47   -
48   - },
49   -
50   -
51   - /**
52   - * Load new notes to fresh list called 'new_notes_list':
53   - * - Replace 'new_notes_list' with new list every n seconds
54   - * - Append new notes to this list after submit
55   - */
56   -
57   - initRefresh:
58   - function() {
59   - // init timer
60   - var intNew = setInterval("NoteList.getNew()", 10000);
61   - },
62   -
63   - replace:
64   - function(html) {
65   - $("#new_notes_list").html(html);
66   - },
67   -
68   - prepend:
69   - function(id, html) {
70   - if(id != this.last_id) {
71   - $("#new_notes_list").prepend(html);
72   - }
73   - },
74   -
75   - getNew:
76   - function() {
77   - // refersh notes list
78   - $.ajax({
79   - type: "GET",
80   - url: this.notes_path,
81   - data: "last_id=" + this.last_id + this.target_params,
82   - dataType: "script"});
83   - },
84   -
85   - refresh:
86   - function() {
87   - // refersh notes list
88   - $.ajax({
89   - type: "GET",
90   - url: this.notes_path,
91   - data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params,
92   - dataType: "script"});
93   - },
94   -
95   -
96   - /**
97   - * Init load of notes:
98   - * 1. Get content with ajax call
99   - * 2. Set content of notes list with loaded one
100   - */
101   -
102   -
103   - getContent:
104   - function() {
105   - $.ajax({
106   - type: "GET",
107   - url: this.notes_path,
108   - data: "?" + this.target_params,
109   - complete: function(){ $('.status').removeClass("loading")},
110   - beforeSend: function() { $('.status').addClass("loading") },
111   - dataType: "script"});
112   - },
113   -
114   - setContent:
115   - function(fid, lid, html) {
116   - this.last_id = lid;
117   - this.first_id = fid;
118   - $("#notes-list").html(html);
119   -
120   - // Init infinite scrolling
121   - this.initLoadMore();
122   - },
123   -
124   -
125   - /**
126   - * Paging for old notes when scroll to bottom:
127   - * 1. Init scroll events with 'initLoadMore'
128   - * 2. Load onlder notes with 'getOld' method
129   - * 3. append old notes to bottom of list with 'append'
130   - *
131   - */
132   - getOld:
133   - function() {
134   - $('.loading').show();
135   - $.ajax({
136   - type: "GET",
137   - url: this.notes_path,
138   - data: "first_id=" + this.first_id + this.target_params,
139   - complete: function(){ $('.status').removeClass("loading")},
140   - beforeSend: function() { $('.status').addClass("loading") },
141   - dataType: "script"});
142   - },
143   -
144   - append:
145   - function(id, html) {
146   - if(this.first_id == id) {
147   - this.disable = true;
148   - } else {
149   - this.first_id = id;
150   - $("#notes-list").append(html);
151   - }
152   - },
153   -
154   - initLoadMore:
155   - function() {
156   - $(document).endlessScroll({
157   - bottomPixels: 400,
158   - fireDelay: 1000,
159   - fireOnce:true,
160   - ceaseFire: function() {
161   - return NoteList.disable;
162   - },
163   - callback: function(i) {
164   - NoteList.getOld();
165   - }
166   - });
167   - }
168   -};
169   -
170   -var PerLineNotes = {
171   - init:
172   - function() {
173   - $(".line_note_link, .line_note_reply_link").live("click", function(e) {
174   - var form = $(".per_line_form");
175   - $(this).closest("tr").after(form);
176   - form.find("#note_line_code").val($(this).attr("line_code"));
177   - form.show();
178   - return false;
179   - });
180   - disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
181   - }
182   -}
app/assets/javascripts/notes.js 0 → 100644
... ... @@ -0,0 +1,293 @@
  1 +var NoteList = {
  2 +
  3 + notes_path: null,
  4 + target_params: null,
  5 + target_id: 0,
  6 + target_type: null,
  7 + top_id: 0,
  8 + bottom_id: 0,
  9 + loading_more_disabled: false,
  10 + reversed: false,
  11 +
  12 + init:
  13 + function(tid, tt, path) {
  14 + this.notes_path = path + ".js";
  15 + this.target_id = tid;
  16 + this.target_type = tt;
  17 + this.reversed = $("#notes-list").hasClass("reversed");
  18 + this.target_params = "&target_type=" + this.target_type + "&target_id=" + this.target_id;
  19 +
  20 + // get initial set of notes
  21 + this.getContent();
  22 +
  23 + $("#notes-list, #new-notes-list").on("ajax:success", ".delete-note", function() {
  24 + $(this).closest('li').fadeOut(function() {
  25 + $(this).remove();
  26 + NoteList.updateVotes();
  27 + });
  28 + });
  29 +
  30 + $(".note-form-holder").on("ajax:before", function(){
  31 + $(".submit_note").disable();
  32 + })
  33 +
  34 + $(".note-form-holder").on("ajax:complete", function(){
  35 + $(".submit_note").enable();
  36 + })
  37 +
  38 + disableButtonIfEmptyField(".note-text", ".submit_note");
  39 +
  40 + $("#note_attachment").change(function(e){
  41 + var val = $('.input-file').val();
  42 + var filename = val.replace(/^.*[\\\/]/, '');
  43 + $(".file_name").text(filename);
  44 + });
  45 +
  46 + if(this.reversed) {
  47 + var textarea = $(".note-text");
  48 + $('.note_advanced_opts').hide();
  49 + textarea.css("height", "40px");
  50 + textarea.on("focus", function(){
  51 + $(this).css("height", "80px");
  52 + $('.note_advanced_opts').show();
  53 + });
  54 + }
  55 + },
  56 +
  57 +
  58 + /**
  59 + * Handle loading the initial set of notes.
  60 + * And set up loading more notes when scrolling to the bottom of the page.
  61 + */
  62 +
  63 +
  64 + /**
  65 + * Gets an inital set of notes.
  66 + */
  67 + getContent:
  68 + function() {
  69 + $.ajax({
  70 + type: "GET",
  71 + url: this.notes_path,
  72 + data: "?" + this.target_params,
  73 + complete: function(){ $('.notes-status').removeClass("loading")},
  74 + beforeSend: function() { $('.notes-status').addClass("loading") },
  75 + dataType: "script"});
  76 + },
  77 +
  78 + /**
  79 + * Called in response to getContent().
  80 + * Replaces the content of #notes-list with the given html.
  81 + */
  82 + setContent:
  83 + function(first_id, last_id, html) {
  84 + this.top_id = first_id;
  85 + this.bottom_id = last_id;
  86 + $("#notes-list").html(html);
  87 +
  88 + // init infinite scrolling
  89 + this.initLoadMore();
  90 +
  91 + // init getting new notes
  92 + if (this.reversed) {
  93 + this.initRefreshNew();
  94 + }
  95 + },
  96 +
  97 +
  98 + /**
  99 + * Handle loading more notes when scrolling to the bottom of the page.
  100 + * The id of the last note in the list is in this.bottom_id.
  101 + *
  102 + * Set up refreshing only new notes after all notes have been loaded.
  103 + */
  104 +
  105 +
  106 + /**
  107 + * Initializes loading more notes when scrolling to the bottom of the page.
  108 + */
  109 + initLoadMore:
  110 + function() {
  111 + $(document).endlessScroll({
  112 + bottomPixels: 400,
  113 + fireDelay: 1000,
  114 + fireOnce:true,
  115 + ceaseFire: function() {
  116 + return NoteList.loading_more_disabled;
  117 + },
  118 + callback: function(i) {
  119 + NoteList.getMore();
  120 + }
  121 + });
  122 + },
  123 +
  124 + /**
  125 + * Gets an additional set of notes.
  126 + */
  127 + getMore:
  128 + function() {
  129 + // only load more notes if there are no "new" notes
  130 + $('.loading').show();
  131 + $.ajax({
  132 + type: "GET",
  133 + url: this.notes_path,
  134 + data: "loading_more=1&" + (this.reversed ? "before_id" : "after_id") + "=" + this.bottom_id + this.target_params,
  135 + complete: function(){ $('.notes-status').removeClass("loading")},
  136 + beforeSend: function() { $('.notes-status').addClass("loading") },
  137 + dataType: "script"});
  138 + },
  139 +
  140 + /**
  141 + * Called in response to getMore().
  142 + * Append notes to #notes-list.
  143 + */
  144 + appendMoreNotes:
  145 + function(id, html) {
  146 + if(id != this.bottom_id) {
  147 + this.bottom_id = id;
  148 + $("#notes-list").append(html);
  149 + }
  150 + },
  151 +
  152 + /**
  153 + * Called in response to getMore().
  154 + * Disables loading more notes when scrolling to the bottom of the page.
  155 + * Initalizes refreshing new notes.
  156 + */
  157 + finishedLoadingMore:
  158 + function() {
  159 + this.loading_more_disabled = true;
  160 +
  161 + // from now on only get new notes
  162 + if (!this.reversed) {
  163 + this.initRefreshNew();
  164 + }
  165 + // make sure we are up to date
  166 + this.updateVotes();
  167 + },
  168 +
  169 +
  170 + /**
  171 + * Handle refreshing and adding of new notes.
  172 + *
  173 + * New notes are all notes that are created after the site has been loaded.
  174 + * The "old" notes are in #notes-list the "new" ones will be in #new-notes-list.
  175 + * The id of the last "old" note is in this.bottom_id.
  176 + */
  177 +
  178 +
  179 + /**
  180 + * Initializes getting new notes every n seconds.
  181 + */
  182 + initRefreshNew:
  183 + function() {
  184 + setInterval("NoteList.getNew()", 10000);
  185 + },
  186 +
  187 + /**
  188 + * Gets the new set of notes.
  189 + */
  190 + getNew:
  191 + function() {
  192 + $.ajax({
  193 + type: "GET",
  194 + url: this.notes_path,
  195 + data: "loading_new=1&after_id=" + (this.reversed ? this.top_id : this.bottom_id) + this.target_params,
  196 + dataType: "script"});
  197 + },
  198 +
  199 + /**
  200 + * Called in response to getNew().
  201 + * Replaces the content of #new-notes-list with the given html.
  202 + */
  203 + replaceNewNotes:
  204 + function(html) {
  205 + $("#new-notes-list").html(html);
  206 + this.updateVotes();
  207 + },
  208 +
  209 + /**
  210 + * Adds a single note to #new-notes-list.
  211 + */
  212 + appendNewNote:
  213 + function(id, html) {
  214 + if (this.reversed) {
  215 + $("#new-notes-list").prepend(html);
  216 + } else {
  217 + $("#new-notes-list").append(html);
  218 + }
  219 + this.updateVotes();
  220 + },
  221 +
  222 + /**
  223 + * Recalculates the votes and updates them (if they are displayed at all).
  224 + *
  225 + * Assumes all relevant notes are displayed (i.e. there are no more notes to
  226 + * load via getMore()).
  227 + * Might produce inaccurate results when not all notes have been loaded and a
  228 + * recalculation is triggered (e.g. when deleting a note).
  229 + */
  230 + updateVotes:
  231 + function() {
  232 + var votes = $("#votes .votes");
  233 + var notes = $("#notes-list, #new-notes-list").find(".note.vote");
  234 +
  235 + // only update if there is a vote display
  236 + if (votes.size()) {
  237 + var upvotes = notes.filter(".upvote").size();
  238 + var downvotes = notes.filter(".downvote").size();
  239 + var votesCount = upvotes + downvotes;
  240 + var upvotesPercent = votesCount ? (100.0 / votesCount * upvotes) : 0;
  241 + var downvotesPercent = votesCount ? (100.0 - upvotesPercent) : 0;
  242 +
  243 + // change vote bar lengths
  244 + votes.find(".bar-success").css("width", upvotesPercent+"%");
  245 + votes.find(".bar-danger").css("width", downvotesPercent+"%");
  246 + // replace vote numbers
  247 + votes.find(".upvotes").text(votes.find(".upvotes").text().replace(/\d+/, upvotes));
  248 + votes.find(".downvotes").text(votes.find(".downvotes").text().replace(/\d+/, downvotes));
  249 + }
  250 + }
  251 +};
  252 +
  253 +var PerLineNotes = {
  254 + init:
  255 + function() {
  256 + /**
  257 + * Called when clicking on the "add note" or "reply" button for a diff line.
  258 + *
  259 + * Shows the note form below the line.
  260 + * Sets some hidden fields in the form.
  261 + */
  262 + $(".diff_file_content").on("click", ".line_note_link, .line_note_reply_link", function(e) {
  263 + var form = $(".per_line_form");
  264 + $(this).closest("tr").after(form);
  265 + form.find("#note_line_code").val($(this).data("lineCode"));
  266 + form.show();
  267 + return false;
  268 + });
  269 +
  270 + disableButtonIfEmptyField(".line-note-text", ".submit_inline_note");
  271 +
  272 + /**
  273 + * Called in response to successfully deleting a note on a diff line.
  274 + *
  275 + * Removes the actual note from view.
  276 + * Removes the reply button if the last note for that line has been removed.
  277 + */
  278 + $(".diff_file_content").on("ajax:success", ".delete-note", function() {
  279 + var trNote = $(this).closest("tr");
  280 + trNote.fadeOut(function() {
  281 + $(this).remove();
  282 + });
  283 +
  284 + // check if this is the last note for this line
  285 + // elements must really be removed for this to work reliably
  286 + var trLine = trNote.prev();
  287 + var trRpl = trNote.next();
  288 + if (trLine.hasClass("line_holder") && trRpl.hasClass("reply")) {
  289 + trRpl.fadeOut(function() { $(this).remove(); });
  290 + }
  291 + });
  292 + }
  293 +}
... ...
app/assets/javascripts/projects.js.coffee
... ... @@ -10,11 +10,15 @@ window.Projects = ->
10 10 $('form #project_default_branch').chosen()
11 11 disableButtonIfEmptyField '#project_name', '.project-submit'
12 12  
13   -# Git clone panel switcher
14 13 $ ->
  14 + # Git clone panel switcher
15 15 scope = $ '.project_clone_holder'
16 16 if scope.length > 0
17 17 $('a, button', scope).click ->
18 18 $('a, button', scope).removeClass 'active'
19 19 $(@).addClass 'active'
20 20 $('#project_clone', scope).val $(@).data 'clone'
  21 +
  22 + # Ref switcher
  23 + $('.project-refs-select').on 'change', ->
  24 + $(@).parents('form').submit()
... ...
app/assets/stylesheets/common.scss
... ... @@ -145,6 +145,19 @@ span.update-author {
145 145 .label {
146 146 background-color: #474D57;
147 147  
  148 + &.label-tag {
  149 + background: none;
  150 + border: none;
  151 + padding:4px 6px;
  152 + color:#444;
  153 + text-shadow:0 0 1px #fff;
  154 +
  155 + &.grouped {
  156 + float: left;
  157 + margin-right: 6px;
  158 + padding: 6px;
  159 + }
  160 + }
148 161 &.label-issue {
149 162 background-color: #eee;
150 163 border: 1px solid #ccc;
... ... @@ -158,6 +171,18 @@ span.update-author {
158 171 padding: 6px;
159 172 }
160 173 }
  174 +
  175 + &.label-success {
  176 + background-color: #8D8;
  177 + color: #333;
  178 + text-shadow: 0 1px 1px white;
  179 + }
  180 +
  181 + &.label-error {
  182 + background-color: #D88;
  183 + color: #333;
  184 + text-shadow: 0 1px 1px white;
  185 + }
161 186 }
162 187  
163 188 .event_label {
... ... @@ -181,11 +206,12 @@ span.update-author {
181 206 }
182 207  
183 208 &.joined {
184   - background-color: #1cb9ff;
  209 + background-color: #1ca9dd;
185 210 }
186 211  
187 212 &.left {
188   - background-color: #ff5057;
  213 + background-color: #888;
  214 + float:none;
189 215 }
190 216 }
191 217  
... ... @@ -414,13 +440,48 @@ p.time {
414 440 }
415 441 }
416 442  
417   -.upvotes {
418   - font-size: 14px;
419   - font-weight: bold;
420   - color: #468847;
421   - text-align: right;
422   - padding: 4px;
423   - margin: 2px;
  443 +.votes {
  444 + font-size: 13px;
  445 + line-height: 15px;
  446 + .progress {
  447 + height: 4px;
  448 + margin: 0;
  449 + .bar {
  450 + float: left;
  451 + height: 100%;
  452 + }
  453 + .bar-success {
  454 + background-color: #468847;
  455 + @include bg-gradient(#62C462, #51A351);
  456 + }
  457 + .bar-danger {
  458 + background-color: #B94A48;
  459 + @include bg-gradient(#EE5F5B, #BD362F);
  460 + }
  461 + }
  462 + .upvotes {
  463 + display: inline-block;
  464 + color: #468847;
  465 + }
  466 + .downvotes {
  467 + display: inline-block;
  468 + color: #B94A48;
  469 + }
  470 +}
  471 +.votes-block {
  472 + margin: 14px 6px 6px 0;
  473 + .downvotes {
  474 + float: right;
  475 + }
  476 +}
  477 +.votes-inline {
  478 + display: inline-block;
  479 + margin: 0 8px;
  480 + .progress {
  481 + display: inline-block;
  482 + padding: 0 0 2px;
  483 + width: 45px;
  484 + }
424 485 }
425 486  
426 487 /* Fix for readme code (stopped it from being yellow) */
... ... @@ -624,7 +685,7 @@ li.note {
624 685 margin-right:40px;
625 686  
626 687 .prev {
627   - @extend .borders;
  688 + @extend .thumbnail;
628 689 height:120px;
629 690 width:175px;
630 691 margin-bottom:10px;
... ... @@ -653,3 +714,31 @@ li.note {
653 714 text-align:center;
654 715 margin-bottom:10px;
655 716 }
  717 +
  718 +.oauth_select_holder {
  719 + padding:20px;
  720 + img {
  721 + padding:5px;
  722 + margin-right:10px;
  723 + }
  724 + .active {
  725 + img {
  726 + border:1px solid #ccc;
  727 + background:$hover;
  728 + @include border-radius(5px);
  729 + }
  730 + }
  731 +}
  732 +
  733 +.btn-build-token {
  734 + float: left;
  735 + padding: 6px 20px;
  736 + margin-right: 12px;
  737 +}
  738 +
  739 +.gitlab-promo {
  740 + a {
  741 + color:#aaa;
  742 + margin-right: 30px;
  743 + }
  744 +}
... ...
app/assets/stylesheets/gitlab_bootstrap/blocks.scss
... ... @@ -65,6 +65,10 @@
65 65 border-color: #CCC;
66 66 @include solid_shade;
67 67  
  68 + &.white {
  69 + background:#fff;
  70 + }
  71 +
68 72 ul {
69 73 margin:0;
70 74 }
... ... @@ -142,4 +146,8 @@
142 146 border:none;
143 147 }
144 148 }
  149 +
  150 + .ui-box-body {
  151 + padding:10px;
  152 + }
145 153 }
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -33,7 +33,29 @@
33 33 .nav-pills a:hover { background-color:#888; }
34 34 .nav-pills .active a { background-color: $style_color; }
35 35 .nav-tabs > li > a, .nav-pills > li > a { color:$style_color; }
36   -.nav-tabs > .active > a { font-weight:bold; }
  36 +.nav.nav-tabs {
  37 + li {
  38 + > a {
  39 + padding:8px 20px;
  40 + margin-right: 7px;
  41 + border-color: #EEE;
  42 + color:#888;
  43 + border-bottom: 1px solid #ddd;
  44 + .badge {
  45 + background-color: #eee;
  46 + color:#888;
  47 + text-shadow:0 1px 1px #fff;
  48 + }
  49 + }
  50 + &.active {
  51 + > a {
  52 + border-color: #CCC;
  53 + border-bottom: 1px solid #fff;
  54 + color:#333;
  55 + }
  56 + }
  57 + }
  58 +}
37 59  
38 60 /** ALERT MESSAGES **/
39 61 .alert-message { @extend .alert; }
... ... @@ -50,3 +72,13 @@ img.lil_av { padding-left: 4px; padding-right:3px; }
50 72 /** HELPERS **/
51 73 .nothing_here_message { text-align:center; padding:20px; color:#777; }
52 74 p.slead { color:#456; font-size:16px; margin-bottom: 12px; font-weight: 200; line-height: 24px; }
  75 +
  76 +/** FORMS **/
  77 +input[type='search'].search-text-input {
  78 + background-image: url("icon-search.png");
  79 + background-repeat: no-repeat;
  80 + background-position: 10px;
  81 + padding-left:25px;
  82 + @include border-radius(4px);
  83 + border:1px solid #ccc;
  84 +}
... ...
app/assets/stylesheets/main.scss
... ... @@ -135,7 +135,6 @@ $hover: #fdf5d9;
135 135 */
136 136 @import "common.scss";
137 137  
138   -
139 138 /**
140 139 * Styles related to specific part of app
141 140 */
... ... @@ -162,6 +161,11 @@ $hover: #fdf5d9;
162 161 @import "sections/notes.scss";
163 162  
164 163 /**
  164 + * This file represent profile styles
  165 + */
  166 +@import "sections/profile.scss";
  167 +
  168 +/**
165 169 * Devise styles
166 170 */
167 171 @import "sections/login.scss";
... ...
app/assets/stylesheets/ref_select.scss
... ... @@ -12,35 +12,45 @@
12 12 width:120px;
13 13 }
14 14  
15   -.project-refs-form .chzn-container {
  15 +.project-refs-form .chzn-container {
16 16 position: relative;
17 17 top: 0;
18 18 left: 0;
19 19 margin-right: 10px;
20 20  
21   - .chzn-drop {
  21 + .chzn-drop {
22 22 margin:7px 0;
23   - border: 1px solid #CCC;
24   - min-width: 300px;
  23 + min-width: 400px;
  24 + border: 2px solid $blue_link;
  25 + @include border-radius(4px);
25 26  
26   - .chzn-results {
  27 + .chzn-results {
27 28 max-height:300px;
  29 +
  30 + .group-result {
  31 + color: $blue_link;
  32 + }
  33 + .active-result {
  34 + &.highlighted {
  35 + background: $blue_link;
  36 + }
  37 + }
28 38 }
29 39  
30 40 .chzn-search input {
31   - min-width:200px;
  41 + min-width:365px;
32 42 }
33 43 }
34 44  
35   - .chzn-single {
  45 + .chzn-single {
36 46 @include bg-gray-gradient;
37 47  
38   - div {
  48 + div {
39 49 background:transparent;
40 50 border-left:none;
41 51 }
42 52  
43   - span {
  53 + span {
44 54 font-weight: normal;
45 55 }
46 56 }
... ...
app/assets/stylesheets/sections/issues.scss
1   -.issue_form_box {
  1 +.issue_form_box {
2 2 @extend .main_box;
3   - .issue_title {
  3 + .issue_title {
4 4 @extend .top_box_content;
5   - .clearfix {
6   - margin-bottom:0px;
7   - input {
  5 + .clearfix {
  6 + margin-bottom:0px;
  7 + input {
8 8 @extend .span8;
9 9 }
10 10 }
11 11 }
12   - .issue_middle_block {
  12 + .issue_middle_block {
13 13 @extend .middle_box_content;
14 14 height:30px;
15   - .issue_assignee {
  15 + .issue_assignee {
16 16 @extend .span6;
17 17 float:left;
18 18 }
19   - .issue_milestone {
  19 + .issue_milestone {
20 20 @extend .span4;
21 21 float:left;
22 22 }
23 23 }
24   - .issue_description {
  24 + .issue_description {
25 25 @extend .bottom_box_content;
26 26 }
27 27 }
28 28  
29   -.issues_table {
30   - .issue {
  29 +.issues_table {
  30 + .issue {
31 31 padding:7px 10px;
32 32  
33   - .issue_check {
  33 + .issue_check {
34 34 float:left;
35 35 padding: 8px 0;
36 36 padding-right: 8px;
37 37 min-width: 15px;
38 38 }
39 39  
40   - p {
  40 + p {
41 41 padding-top:0;
42 42 padding-bottom:2px;
43 43 }
44 44  
45   - img.avatar {
  45 + img.avatar {
46 46 width:32px;
47 47 margin-top:4px;
48 48 }
49 49 }
50 50 }
51 51  
52   -input.check_all_issues {
  52 +input.check_all_issues {
53 53 float:left;
54 54 padding: 0;
55 55 margin:0;
... ... @@ -59,8 +59,8 @@ input.check_all_issues {
59 59 height: 22px;
60 60 }
61 61  
62   -.issues_content {
63   - .title {
  62 +.issues_content {
  63 + .title {
64 64 height: 40px;
65 65 }
66 66 }
... ... @@ -70,30 +70,30 @@ input.check_all_issues {
70 70 @media (min-width: 1200px) { .issues_filters select { width:220px; } }
71 71  
72 72  
73   -#issues-table-holder {
74   - .issues_filters {
75   - form {
  73 +#issues-table-holder {
  74 + .issues_filters {
  75 + form {
76 76 padding:0;
77 77 margin:0;
78 78 margin-top:7px
79 79 }
80   - }
  80 + }
81 81  
82   - .issues_bulk_update {
  82 + .issues_bulk_update {
83 83 margin: 0;
84   - form {
  84 + form {
85 85 padding:0;
86 86 margin:0;
87 87 margin-top:7px
88 88 }
89   - .update_selected_issues {
  89 + .update_selected_issues {
90 90 position:relative;
91 91 top:-2px;
92 92 margin-left:4px;
93 93 float:left;
94 94 }
95   -
96   - .update_issues_text {
  95 +
  96 + .update_issues_text {
97 97 padding:3px;
98 98 line-height: 18px;
99 99 float:left;
... ... @@ -101,10 +101,11 @@ input.check_all_issues {
101 101 }
102 102 }
103 103  
104   -#update_status {
  104 +#update_status {
105 105 width:100px;
106 106 }
107 107  
  108 +
108 109 /**
109 110 * Milestones list
110 111 *
... ...
app/assets/stylesheets/sections/merge_requests.scss
1   -/**
  1 +/**
2 2 * MR form
3 3 *
4 4 */
5 5  
6   -.mr_branch_box {
  6 +.mr_branch_box {
7 7 @extend .ui-box;
8 8 margin-bottom:20px;
9 9  
10   - .body {
  10 + .body {
11 11 background:#f1f1f1;
12 12 }
13 13  
... ... @@ -17,19 +17,19 @@
17 17 * MR -> show: Automerge widget
18 18 *
19 19 */
20   -.automerge_widget {
21   - &.can_be_merged {
  20 +.automerge_widget {
  21 + &.can_be_merged {
22 22 background: #DFF0D8;
23 23 }
24 24  
25   - form {
  25 + form {
26 26 margin-bottom:0;
27   - .clearfix {
  27 + .clearfix {
28 28 margin-bottom:0;
29 29 }
30 30 }
31 31  
32   - .accept_group {
  32 + .accept_group {
33 33 float:left;
34 34 border: 1px solid #ADA;
35 35 padding: 2px;
... ... @@ -37,29 +37,29 @@
37 37 border-radius: 5px;
38 38 background: #CEB;
39 39  
40   - .accept_merge_request {
  40 + .accept_merge_request {
41 41 font-size:13px;
42 42 float:left;
43 43 }
44   - .remove_branch_holder {
  44 + .remove_branch_holder {
45 45 margin-left:20px;
46 46 margin-right:10px;
47 47 float:left;
48 48 }
49   - label {
  49 + label {
50 50 color:#444;
51 51 }
52 52 }
53 53  
54 54  
55   - .how_to_merge_link {
  55 + .how_to_merge_link {
56 56 @extend .primary;
57 57 }
58 58 }
59 59  
60   -.mr_nav_tabs {
61   - li {
62   - a {
  60 +.mr_nav_tabs {
  61 + li {
  62 + a {
63 63 font-weight:bold;
64 64 padding:8px 20px;
65 65 text-align:center;
... ... @@ -67,19 +67,19 @@
67 67 }
68 68 }
69 69  
70   -li.merge_request {
  70 +li.merge_request {
71 71 padding:7px 10px;
72   - img.avatar {
  72 + img.avatar {
73 73 width: 32px;
74 74 margin-top: 4px;
75 75 }
76   - p {
  76 + p {
77 77 padding: 0px;
78 78 padding-bottom: 2px;
79 79 }
80 80 }
81 81  
82   -.merge_in_progress {
  82 +.merge_in_progress {
83 83 @extend .padded;
84 84 @extend .append-bottom-10;
85 85 }
... ... @@ -88,22 +88,21 @@ li.merge_request {
88 88 @include round-borders-all(4px);
89 89 padding:2px 4px;
90 90 border:none;
91   - font-size:13px;
  91 + font-size:14px;
92 92 background: #474D57;
93 93 color:#fff;
94   - font-weight:bold;
95   - font-family: monospace;
  94 + font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace;
96 95 }
97 96  
98   -.mr_source_commit,
99   -.mr_target_commit {
100   - .commit {
  97 +.mr_source_commit,
  98 +.mr_target_commit {
  99 + .commit {
101 100 margin:0;
102 101 padding:0;
103 102 padding: 5px;
104 103 margin-bottom: 5px;
105 104 .avatar { position:relative }
106   - .row_title {
  105 + .row_title {
107 106 color:#444;
108 107 }
109 108 .commit-author-name,
... ... @@ -113,12 +112,12 @@ li.merge_request {
113 112 display:none;
114 113 }
115 114 list-style:none;
116   - &:hover {
  115 + &:hover {
117 116 background:none;
118 117 }
119 118 }
120 119 }
121 120  
122   -.mr_direction_tip {
  121 +.mr_direction_tip {
123 122 margin-top:40px
124 123 }
... ...
app/assets/stylesheets/sections/nav.scss
... ... @@ -55,7 +55,6 @@ ul.main_menu {
55 55  
56 56 &.current {
57 57 background-color:#D5D5D5;
58   - border-bottom: 1px solid #AAA;
59 58 border-right: 1px solid #BBB;
60 59 border-left: 1px solid #BBB;
61 60 border-radius: 0 0 1px 1px;
... ...
app/assets/stylesheets/sections/notes.scss
... ... @@ -3,17 +3,13 @@
3 3 *
4 4 */
5 5 #notes-list,
6   -#new_notes_list {
  6 +#new-notes-list {
7 7 display:block;
8 8 list-style:none;
9 9 margin:0px;
10 10 padding:0px;
11 11 }
12 12  
13   -#new_notes_list li:last-child{
14   - border-bottom:1px solid #aaa;
15   -}
16   -
17 13 .issue_notes,
18 14 .wiki_notes {
19 15 .note_content {
... ... @@ -30,9 +26,6 @@
30 26 }
31 27  
32 28 #new_note {
33   - .note-text {
34   - height:40px;
35   - }
36 29 .attach_holder {
37 30 display:none;
38 31 }
... ... @@ -48,7 +41,6 @@
48 41  
49 42 .note {
50 43 padding: 8px 0;
51   - border-bottom: 1px solid #eee;
52 44 overflow: hidden;
53 45 display: block;
54 46 img {float: left; margin-right: 10px;}
... ... @@ -70,6 +62,23 @@
70 62 .delete-note { display:block; }
71 63 }
72 64 }
  65 +#notes-list:not(.reversed) .note,
  66 +#new-notes-list:not(.reversed) .note {
  67 + border-bottom: 1px solid #eee;
  68 +}
  69 +#notes-list.reversed .note,
  70 +#new-notes-list.reversed .note {
  71 + border-top: 1px solid #eee;
  72 +}
  73 +
  74 +/* mark vote notes */
  75 +.voting_notes .note {
  76 + padding: 8px 0;
  77 +}
  78 +
  79 +.notes-status {
  80 + margin: 18px;
  81 +}
73 82  
74 83  
75 84 p.notify_controls input{
... ... @@ -213,7 +222,7 @@ td .line_note_link {
213 222 }
214 223 }
215 224  
216   -.note-text {
  225 +.note-text {
217 226 border: 1px solid #aaa;
218 227 box-shadow:none;
219 228 }
... ...
app/assets/stylesheets/sections/profile.scss 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +.profile_history {
  2 + .event_feed {
  3 + min-height:20px;
  4 + .avatar {
  5 + width:20px;
  6 + }
  7 + }
  8 +}
... ...
app/contexts/notes/load_context.rb
... ... @@ -3,30 +3,31 @@ module Notes
3 3 def execute
4 4 target_type = params[:target_type]
5 5 target_id = params[:target_id]
6   - first_id = params[:first_id]
7   - last_id = params[:last_id]
  6 + after_id = params[:after_id]
  7 + before_id = params[:before_id]
8 8  
9 9  
10 10 @notes = case target_type
11   - when "commit"
12   - then project.commit_notes(project.commit(target_id)).fresh.limit(20)
13   - when "snippet"
14   - then project.snippets.find(target_id).notes
15   - when "wall"
16   - then project.common_notes.order("created_at DESC").fresh.limit(50)
  11 + when "commit"
  12 + project.commit_notes(project.commit(target_id)).fresh.limit(20)
17 13 when "issue"
18   - then project.issues.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
  14 + project.issues.find(target_id).notes.inc_author.fresh.limit(20)
19 15 when "merge_request"
20   - then project.merge_requests.find(target_id).notes.inc_author.order("created_at DESC").limit(20)
  16 + project.merge_requests.find(target_id).notes.inc_author.fresh.limit(20)
  17 + when "snippet"
  18 + project.snippets.find(target_id).notes.fresh
  19 + when "wall"
  20 + # this is the only case, where the order is DESC
  21 + project.common_notes.order("created_at DESC, id DESC").limit(50)
21 22 when "wiki"
22   - then project.wikis.reverse.map {|w| w.notes.fresh }.flatten[0..20]
  23 + project.wiki_notes.limit(20)
23 24 end
24 25  
25   - @notes = if last_id
26   - @notes.where("id > ?", last_id)
27   - elsif first_id
28   - @notes.where("id < ?", first_id)
29   - else
  26 + @notes = if after_id
  27 + @notes.where("id > ?", after_id)
  28 + elsif before_id
  29 + @notes.where("id < ?", before_id)
  30 + else
30 31 @notes
31 32 end
32 33 end
... ...
app/controllers/admin/dashboard_controller.rb
1   -class Admin::DashboardController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
5   -
  1 +class Admin::DashboardController < AdminController
6 2 def index
7 3 @workers = Resque.workers
8 4 @pending_jobs = Resque.size(:post_receive)
... ...
app/controllers/admin/hooks_controller.rb
1   -class Admin::HooksController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
5   -
  1 +class Admin::HooksController < AdminController
6 2 def index
7 3 @hooks = SystemHook.all
8 4 @hook = SystemHook.new
... ... @@ -15,7 +11,7 @@ class Admin::HooksController &lt; ApplicationController
15 11 redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
16 12 else
17 13 @hooks = SystemHook.all
18   - render :index
  14 + render :index
19 15 end
20 16 end
21 17  
... ...
app/controllers/admin/logs_controller.rb
1   -class Admin::LogsController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
  1 +class Admin::LogsController < AdminController
5 2 end
6   -
... ...
app/controllers/admin/projects_controller.rb
1   -class Admin::ProjectsController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
  1 +class Admin::ProjectsController < AdminController
5 2 before_filter :admin_project, only: [:edit, :show, :update, :destroy, :team_update]
6 3  
7 4 def index
... ... @@ -43,7 +40,7 @@ class Admin::ProjectsController &lt; ApplicationController
43 40 def update
44 41 owner_id = params[:project].delete(:owner_id)
45 42  
46   - if owner_id
  43 + if owner_id
47 44 @admin_project.owner = User.find(owner_id)
48 45 end
49 46  
... ... @@ -60,7 +57,7 @@ class Admin::ProjectsController &lt; ApplicationController
60 57 redirect_to admin_projects_url, notice: 'Project was successfully deleted.'
61 58 end
62 59  
63   - private
  60 + private
64 61  
65 62 def admin_project
66 63 @admin_project = Project.find_by_code(params[:id])
... ...
app/controllers/admin/resque_controller.rb
1   -class Admin::ResqueController < ApplicationController
2   - layout 'admin'
  1 +class Admin::ResqueController < AdminController
3 2 def show
4 3 end
5   -end
6 4 \ No newline at end of file
  5 +end
... ...
app/controllers/admin/team_members_controller.rb
1   -class Admin::TeamMembersController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
5   -
  1 +class Admin::TeamMembersController < AdminController
6 2 def edit
7 3 @admin_team_member = UsersProject.find(params[:id])
8 4 end
... ...
app/controllers/admin/users_controller.rb
1   -class Admin::UsersController < ApplicationController
2   - layout "admin"
3   - before_filter :authenticate_user!
4   - before_filter :authenticate_admin!
5   -
  1 +class Admin::UsersController < AdminController
6 2 def index
7 3 @admin_users = User.scoped
8 4 @admin_users = @admin_users.filter(params[:filter])
... ... @@ -24,7 +20,7 @@ class Admin::UsersController &lt; ApplicationController
24 20 @admin_user = User.find(params[:id])
25 21  
26 22 UsersProject.user_bulk_import(
27   - @admin_user,
  23 + @admin_user,
28 24 params[:project_ids],
29 25 params[:project_access]
30 26 )
... ... @@ -41,22 +37,22 @@ class Admin::UsersController &lt; ApplicationController
41 37 @admin_user = User.find(params[:id])
42 38 end
43 39  
44   - def block
  40 + def block
45 41 @admin_user = User.find(params[:id])
46 42  
47 43 if @admin_user.block
48 44 redirect_to :back, alert: "Successfully blocked"
49   - else
  45 + else
50 46 redirect_to :back, alert: "Error occured. User was not blocked"
51 47 end
52 48 end
53 49  
54   - def unblock
  50 + def unblock
55 51 @admin_user = User.find(params[:id])
56 52  
57 53 if @admin_user.update_attribute(:blocked, false)
58 54 redirect_to :back, alert: "Successfully unblocked"
59   - else
  55 + else
60 56 redirect_to :back, alert: "Error occured. User was not unblocked"
61 57 end
62 58 end
... ...
app/controllers/admin_controller.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +# Provides a base class for Admin controllers to subclass
  2 +#
  3 +# Automatically sets the layout and ensures an administrator is logged in
  4 +class AdminController < ApplicationController
  5 + layout 'admin'
  6 + before_filter :authenticate_admin!
  7 +
  8 + def authenticate_admin!
  9 + return render_404 unless current_user.is_admin?
  10 + end
  11 +end
... ...
app/controllers/application_controller.rb
... ... @@ -84,10 +84,6 @@ class ApplicationController &lt; ActionController::Base
84 84 abilities << Ability
85 85 end
86 86  
87   - def authenticate_admin!
88   - return render_404 unless current_user.is_admin?
89   - end
90   -
91 87 def authorize_project!(action)
92 88 return access_denied! unless can?(current_user, action, project)
93 89 end
... ...
app/controllers/commits_controller.rb
... ... @@ -64,7 +64,7 @@ class CommitsController &lt; ApplicationController
64 64 @commit.to_patch,
65 65 type: "text/plain",
66 66 disposition: 'attachment',
67   - filename: "#{@commit.id.patch}"
  67 + filename: "#{@commit.id}.patch"
68 68 )
69 69 end
70 70  
... ...
app/controllers/issues_controller.rb
... ... @@ -17,7 +17,7 @@ class IssuesController &lt; ApplicationController
17 17 before_filter :authorize_write_issue!, only: [:new, :create]
18 18  
19 19 # Allow modify issue
20   - before_filter :authorize_modify_issue!, only: [:close, :edit, :update]
  20 + before_filter :authorize_modify_issue!, only: [:edit, :update]
21 21  
22 22 # Allow destroy issue
23 23 before_filter :authorize_admin_issue!, only: [:destroy]
... ... @@ -87,8 +87,6 @@ class IssuesController &lt; ApplicationController
87 87 end
88 88  
89 89 def destroy
90   - return access_denied! unless can?(current_user, :admin_issue, @issue)
91   -
92 90 @issue.destroy
93 91  
94 92 respond_to do |format|
... ...
app/controllers/omniauth_callbacks_controller.rb
1 1 class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  2 + Gitlab.config.omniauth_providers.each do |provider|
  3 + define_method provider['name'] do
  4 + handle_omniauth
  5 + end
  6 + end
2 7  
3 8 # Extend the standard message generation to accept our custom exception
4 9 def failure_message
... ... @@ -9,7 +14,7 @@ class OmniauthCallbacksController &lt; Devise::OmniauthCallbacksController
9 14 error ||= env["omniauth.error.type"].to_s
10 15 error.to_s.humanize if error
11 16 end
12   -
  17 +
13 18 def ldap
14 19 # We only find ourselves here if the authentication to LDAP was successful.
15 20 @user = User.find_for_ldap_auth(request.env["omniauth.auth"], current_user)
... ... @@ -19,4 +24,27 @@ class OmniauthCallbacksController &lt; Devise::OmniauthCallbacksController
19 24 sign_in_and_redirect @user
20 25 end
21 26  
  27 + private
  28 +
  29 + def handle_omniauth
  30 + oauth = request.env['omniauth.auth']
  31 + provider, uid = oauth['provider'], oauth['uid']
  32 +
  33 + if current_user
  34 + # Change a logged-in user's authentication method:
  35 + current_user.extern_uid = uid
  36 + current_user.provider = provider
  37 + current_user.save
  38 + redirect_to profile_path
  39 + else
  40 + @user = User.find_or_new_for_omniauth(oauth)
  41 +
  42 + if @user
  43 + sign_in_and_redirect @user
  44 + else
  45 + flash[:notice] = "There's no such user!"
  46 + redirect_to new_user_session_path
  47 + end
  48 + end
  49 + end
22 50 end
... ...
app/controllers/profile_controller.rb
... ... @@ -16,9 +16,6 @@ class ProfileController &lt; ApplicationController
16 16 def token
17 17 end
18 18  
19   - def password
20   - end
21   -
22 19 def password_update
23 20 params[:user].reject!{ |k, v| k != "password" && k != "password_confirmation"}
24 21  
... ... @@ -32,10 +29,14 @@ class ProfileController &lt; ApplicationController
32 29  
33 30 def reset_private_token
34 31 current_user.reset_authentication_token!
35   - redirect_to profile_token_path
  32 + redirect_to profile_account_path
  33 + end
  34 +
  35 + def history
  36 + @events = current_user.recent_events.page(params[:page]).per(20)
36 37 end
37 38  
38   - private
  39 + private
39 40  
40 41 def user
41 42 @user = current_user
... ...
app/controllers/team_members_controller.rb
... ... @@ -5,7 +5,10 @@ class TeamMembersController &lt; ApplicationController
5 5 # Authorize
6 6 before_filter :add_project_abilities
7 7 before_filter :authorize_read_project!
8   - before_filter :authorize_admin_project!, except: [:show]
  8 + before_filter :authorize_admin_project!, except: [:index, :show]
  9 +
  10 + def index
  11 + end
9 12  
10 13 def show
11 14 @team_member = project.users_projects.find(params[:id])
... ... @@ -22,7 +25,7 @@ class TeamMembersController &lt; ApplicationController
22 25 params[:project_access]
23 26 )
24 27  
25   - redirect_to team_project_path(@project)
  28 + redirect_to project_team_index_path(@project)
26 29 end
27 30  
28 31 def update
... ... @@ -32,7 +35,7 @@ class TeamMembersController &lt; ApplicationController
32 35 unless @team_member.valid?
33 36 flash[:alert] = "User should have at least one role"
34 37 end
35   - redirect_to team_project_path(@project)
  38 + redirect_to project_team_index_path(@project)
36 39 end
37 40  
38 41 def destroy
... ... @@ -40,7 +43,7 @@ class TeamMembersController &lt; ApplicationController
40 43 @team_member.destroy
41 44  
42 45 respond_to do |format|
43   - format.html { redirect_to team_project_path(@project) }
  46 + format.html { redirect_to project_team_index_path(@project) }
44 47 format.js { render nothing: true }
45 48 end
46 49 end
... ...
app/decorators/commit_decorator.rb
... ... @@ -16,7 +16,7 @@ class CommitDecorator &lt; ApplicationDecorator
16 16 # In case this first line is longer than 80 characters, it is cut off
17 17 # after 70 characters and ellipses (`&hellp;`) are appended.
18 18 def title
19   - return no_commit_message unless safe_message
  19 + return no_commit_message if safe_message.blank?
20 20  
21 21 title_end = safe_message.index(/\n/)
22 22 if (!title_end && safe_message.length > 80) || (title_end && title_end > 80)
... ...
app/helpers/application_helper.rb
... ... @@ -62,7 +62,7 @@ module ApplicationHelper
62 62 { label: "#{@project.name} / Wall", url: wall_project_path(@project) },
63 63 { label: "#{@project.name} / Tree", url: tree_project_ref_path(@project, @project.root_ref) },
64 64 { label: "#{@project.name} / Commits", url: project_commits_path(@project) },
65   - { label: "#{@project.name} / Team", url: team_project_path(@project) }
  65 + { label: "#{@project.name} / Team", url: project_team_index_path(@project) }
66 66 ]
67 67 end
68 68  
... ... @@ -104,7 +104,8 @@ module ApplicationHelper
104 104  
105 105 # Profile Area
106 106 when :profile; current_page?(controller: "profile", action: :show)
107   - when :password; current_page?(controller: "profile", action: :password)
  107 + when :history; current_page?(controller: "profile", action: :history)
  108 + when :account; current_page?(controller: "profile", action: :account)
108 109 when :token; current_page?(controller: "profile", action: :token)
109 110 when :design; current_page?(controller: "profile", action: :design)
110 111 when :ssh_keys; controller.controller_name == "keys"
... ... @@ -135,4 +136,10 @@ module ApplicationHelper
135 136 "Never"
136 137 end
137 138 end
  139 +
  140 + def authbutton(provider, size = 64)
  141 + file_name = "#{provider.to_s.split('_').first}_#{size}.png"
  142 + image_tag("authbuttons/#{file_name}",
  143 + alt: "Sign in with #{provider.to_s.titleize}")
  144 + end
138 145 end
... ...
app/helpers/gitlab_markdown_helper.rb
... ... @@ -11,7 +11,9 @@ module GitlabMarkdownHelper
11 11 # explicitly produce the correct linking behavior (i.e.
12 12 # "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
13 13 def link_to_gfm(body, url, html_options = {})
14   - gfm_body = gfm(body, html_options)
  14 + return "" if body.blank?
  15 +
  16 + gfm_body = gfm(escape_once(body), html_options)
15 17  
16 18 gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
17 19 "</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".length +1
... ...
app/helpers/notes_helper.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +module NotesHelper
  2 + def loading_more_notes?
  3 + params[:loading_more].present?
  4 + end
  5 +
  6 + def loading_new_notes?
  7 + params[:loading_new].present?
  8 + end
  9 +
  10 + def note_vote_class(note)
  11 + if note.upvote?
  12 + "vote upvote"
  13 + elsif note.downvote?
  14 + "vote downvote"
  15 + end
  16 + end
  17 +end
... ...
app/helpers/profile_helper.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +module ProfileHelper
  2 + def oauth_active_class provider
  3 + if current_user.provider == provider.to_s
  4 + 'active'
  5 + end
  6 + end
  7 +end
... ...
app/helpers/projects_helper.rb
... ... @@ -2,5 +2,9 @@ module ProjectsHelper
2 2 def grouper_project_members(project)
3 3 @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
4 4 end
  5 +
  6 + def remove_from_team_message(project, member)
  7 + "You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
  8 + end
5 9 end
6 10  
... ...
app/helpers/tab_helper.rb
... ... @@ -8,7 +8,7 @@ module TabHelper
8 8 end
9 9  
10 10 def project_tab_class
11   - [:show, :files, :team, :edit, :update].each do |action|
  11 + [:show, :files, :edit, :update].each do |action|
12 12 return "current" if current_page?(controller: "projects", action: action, id: @project)
13 13 end
14 14  
... ...
app/helpers/tree_helper.rb
... ... @@ -18,7 +18,8 @@ module TreeHelper
18 18 end
19 19  
20 20 def tree_full_path(content)
21   - if params[:path]
  21 + content.name.force_encoding('utf-8')
  22 + if params[:path]
22 23 File.join(params[:path], content.name)
23 24 else
24 25 content.name
... ...
app/models/event.rb
... ... @@ -35,13 +35,21 @@ class Event &lt; ActiveRecord::Base
35 35 end
36 36  
37 37 # Next events currently enabled for system
38   - # - push
  38 + # - push
39 39 # - new issue
40 40 # - merge request
41 41 def allowed?
42 42 push? || issue? || merge_request? || membership_changed?
43 43 end
44 44  
  45 + def project_name
  46 + if project
  47 + project.name
  48 + else
  49 + "(deleted)"
  50 + end
  51 + end
  52 +
45 53 def push?
46 54 action == self.class::Pushed && valid_push?
47 55 end
... ... @@ -58,31 +66,31 @@ class Event &lt; ActiveRecord::Base
58 66 action == self.class::Reopened
59 67 end
60 68  
61   - def issue?
  69 + def issue?
62 70 target_type == "Issue"
63 71 end
64 72  
65   - def merge_request?
  73 + def merge_request?
66 74 target_type == "MergeRequest"
67 75 end
68 76  
69   - def new_issue?
70   - target_type == "Issue" &&
  77 + def new_issue?
  78 + target_type == "Issue" &&
71 79 action == Created
72 80 end
73 81  
74   - def new_merge_request?
75   - target_type == "MergeRequest" &&
  82 + def new_merge_request?
  83 + target_type == "MergeRequest" &&
76 84 action == Created
77 85 end
78 86  
79   - def changed_merge_request?
80   - target_type == "MergeRequest" &&
  87 + def changed_merge_request?
  88 + target_type == "MergeRequest" &&
81 89 [Closed, Reopened].include?(action)
82 90 end
83 91  
84   - def changed_issue?
85   - target_type == "Issue" &&
  92 + def changed_issue?
  93 + target_type == "Issue" &&
86 94 [Closed, Reopened].include?(action)
87 95 end
88 96  
... ... @@ -98,7 +106,7 @@ class Event &lt; ActiveRecord::Base
98 106 joined? || left?
99 107 end
100 108  
101   - def issue
  109 + def issue
102 110 target if target_type == "Issue"
103 111 end
104 112  
... ... @@ -106,7 +114,7 @@ class Event &lt; ActiveRecord::Base
106 114 target if target_type == "MergeRequest"
107 115 end
108 116  
109   - def author
  117 + def author
110 118 @author ||= User.find(author_id)
111 119 end
112 120  
... ... @@ -119,7 +127,7 @@ class Event &lt; ActiveRecord::Base
119 127 'joined'
120 128 elsif left?
121 129 'left'
122   - else
  130 + else
123 131 "opened"
124 132 end
125 133 end
... ...
app/models/issue.rb
1 1 class Issue < ActiveRecord::Base
2 2 include IssueCommonality
3   - include Upvote
  3 + include Votes
4 4  
5 5 acts_as_taggable_on :labels
6 6  
... ...
app/models/merge_request.rb
... ... @@ -2,7 +2,7 @@ require File.join(Rails.root, &quot;app/models/commit&quot;)
2 2  
3 3 class MergeRequest < ActiveRecord::Base
4 4 include IssueCommonality
5   - include Upvote
  5 + include Votes
6 6  
7 7 BROKEN_DIFF = "--broken-diff"
8 8  
... ...
app/models/note.rb
... ... @@ -36,7 +36,7 @@ class Note &lt; ActiveRecord::Base
36 36 scope :today, where("created_at >= :date", date: Date.today)
37 37 scope :last_week, where("created_at >= :date", date: (Date.today - 7.days))
38 38 scope :since, lambda { |day| where("created_at >= :date", date: (day)) }
39   - scope :fresh, order("created_at DESC")
  39 + scope :fresh, order("created_at ASC, id ASC")
40 40 scope :inc_author_project, includes(:project, :author)
41 41 scope :inc_author, includes(:author)
42 42  
... ... @@ -105,6 +105,12 @@ class Note &lt; ActiveRecord::Base
105 105 def upvote?
106 106 note.start_with?('+1') || note.start_with?(':+1:')
107 107 end
  108 +
  109 + # Returns true if this is a downvote note,
  110 + # otherwise false is returned
  111 + def downvote?
  112 + note.start_with?('-1') || note.start_with?(':-1:')
  113 + end
108 114 end
109 115 # == Schema Information
110 116 #
... ...
app/models/project.rb
... ... @@ -171,6 +171,10 @@ class Project &lt; ActiveRecord::Base
171 171 end
172 172 end
173 173  
  174 + def wiki_notes
  175 + Note.where(noteable_id: wikis.map(&:id), noteable_type: 'Wiki', project_id: self.id)
  176 + end
  177 +
174 178 def project_id
175 179 self.id
176 180 end
... ...
app/models/tree.rb
... ... @@ -16,7 +16,7 @@ class Tree
16 16 def initialize(raw_tree, project, ref = nil, path = nil)
17 17 @project, @ref, @path = project, ref, path,
18 18 @tree = if path
19   - raw_tree / path
  19 + raw_tree / path.dup.force_encoding('ascii-8bit')
20 20 else
21 21 raw_tree
22 22 end
... ...
app/models/user.rb
... ... @@ -86,33 +86,20 @@ class User &lt; ActiveRecord::Base
86 86 where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
87 87 end
88 88  
89   - def self.find_for_ldap_auth(auth, signed_in_resource=nil)
90   - uid = auth.info.uid
91   - provider = auth.provider
92   - name = auth.info.name.force_encoding("utf-8")
93   - email = auth.info.email.downcase unless auth.info.email.nil?
94   - raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil?
95   -
96   - if @user = User.find_by_extern_uid_and_provider(uid, provider)
97   - @user
98   - # workaround for backward compatibility
99   - elsif @user = User.find_by_email(email)
100   - logger.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}"
101   - @user.update_attributes(:extern_uid => uid, :provider => provider)
102   - @user
103   - else
104   - logger.info "Creating user from LDAP login {uid => #{uid}, name => #{name}, email => #{email}}"
105   - password = Devise.friendly_token[0, 8].downcase
106   - @user = User.create(
107   - :extern_uid => uid,
108   - :provider => provider,
109   - :name => name,
110   - :email => email,
111   - :password => password,
112   - :password_confirmation => password,
113   - :projects_limit => Gitlab.config.default_projects_limit
114   - )
115   - end
  89 + def self.create_from_omniauth(auth, ldap = false)
  90 + gitlab_auth.create_from_omniauth(auth, ldap)
  91 + end
  92 +
  93 + def self.find_or_new_for_omniauth(auth)
  94 + gitlab_auth.find_or_new_for_omniauth(auth)
  95 + end
  96 +
  97 + def self.find_for_ldap_auth(auth, signed_in_resource = nil)
  98 + gitlab_auth.find_for_ldap_auth(auth, signed_in_resource)
  99 + end
  100 +
  101 + def self.gitlab_auth
  102 + Gitlab::Auth.new
116 103 end
117 104  
118 105 def self.search query
... ... @@ -148,4 +135,3 @@ end
148 135 # bio :string(255)
149 136 # blocked :boolean(1) default(FALSE), not null
150 137 #
151   -
... ...
app/models/wiki.rb
... ... @@ -28,7 +28,6 @@ class Wiki &lt; ActiveRecord::Base
28 28 end
29 29 new_wiki
30 30 end
31   -
32 31 end
33 32 end
34 33 # == Schema Information
... ...
app/observers/project_observer.rb
... ... @@ -4,6 +4,18 @@ class ProjectObserver &lt; ActiveRecord::Observer
4 4 end
5 5  
6 6 def after_destroy(project)
  7 + log_info("Project \"#{project.name}\" was removed")
  8 +
7 9 project.destroy_repository
8 10 end
  11 +
  12 + def after_create project
  13 + log_info("#{project.owner.name} created a new project \"#{project.name}\"")
  14 + end
  15 +
  16 + protected
  17 +
  18 + def log_info message
  19 + Gitlab::AppLogger.info message
  20 + end
9 21 end
... ...
app/observers/user_observer.rb
1 1 class UserObserver < ActiveRecord::Observer
2 2 def after_create(user)
  3 + log_info("User \"#{user.name}\" (#{user.email}) was created")
  4 +
3 5 Notify.new_user_email(user.id, user.password).deliver
4 6 end
  7 +
  8 + def after_destroy user
  9 + log_info("User \"#{user.name}\" (#{user.email}) was removed")
  10 + end
  11 +
  12 + protected
  13 +
  14 + def log_info message
  15 + Gitlab::AppLogger.info message
  16 + end
5 17 end
... ...
app/observers/users_project_observer.rb
... ... @@ -14,8 +14,8 @@ class UsersProjectObserver &lt; ActiveRecord::Observer
14 14  
15 15 def after_destroy(users_project)
16 16 Event.create(
17   - project_id: users_project.project.id,
18   - action: Event::Left,
  17 + project_id: users_project.project.id,
  18 + action: Event::Left,
19 19 author_id: users_project.user.id
20 20 )
21 21 end
... ...
app/roles/upvote.rb
... ... @@ -1,6 +0,0 @@
1   -module Upvote
2   - # Return the number of +1 comments (upvotes)
3   - def upvotes
4   - notes.select(&:upvote?).size
5   - end
6   -end
app/roles/votes.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +module Votes
  2 + # Return the number of +1 comments (upvotes)
  3 + def upvotes
  4 + notes.select(&:upvote?).size
  5 + end
  6 +
  7 + def upvotes_in_percent
  8 + if votes_count.zero?
  9 + 0
  10 + else
  11 + 100.0 / votes_count * upvotes
  12 + end
  13 + end
  14 +
  15 + # Return the number of -1 comments (downvotes)
  16 + def downvotes
  17 + notes.select(&:downvote?).size
  18 + end
  19 +
  20 + def downvotes_in_percent
  21 + if votes_count.zero?
  22 + 0
  23 + else
  24 + 100.0 - upvotes_in_percent
  25 + end
  26 + end
  27 +
  28 + # Return the total number of votes
  29 + def votes_count
  30 + upvotes + downvotes
  31 + end
  32 +end
... ...
app/views/admin/logs/show.html.haml
1   -.file_holder#README
2   - .file_title
3   - %i.icon-file
4   - githost.log
5   - .file_content.logs
6   - %ol
7   - - Gitlab::Logger.read_latest.each do |line|
8   - %li
9   - %p= line
  1 +%ul.nav.nav-tabs.log-tabs
  2 + %li.active
  3 + = link_to "githost.log", "#githost", 'data-toggle' => 'tab'
  4 + %li
  5 + = link_to "application.log", "#application", 'data-toggle' => 'tab'
  6 +.tab-content
  7 + .tab-pane.active#githost
  8 + .file_holder#README
  9 + .file_title
  10 + %i.icon-file
  11 + githost.log
  12 + .file_content.logs
  13 + %ol
  14 + - Gitlab::GitLogger.read_latest.each do |line|
  15 + %li
  16 + %p= line
  17 + .tab-pane#application
  18 + .file_holder#README
  19 + .file_title
  20 + %i.icon-file
  21 + application.log
  22 + .file_content.logs
  23 + %ol
  24 + - Gitlab::AppLogger.read_latest.each do |line|
  25 + %li
  26 + %p= line
... ...
app/views/admin/projects/_form.html.haml
... ... @@ -32,7 +32,7 @@
32 32 - unless project.new_record?
33 33 .clearfix
34 34 = f.label :owner_id
35   - .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }
  35 + .input= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
36 36  
37 37 - if project.repo_exists?
38 38 .clearfix
... ... @@ -69,7 +69,6 @@
69 69  
70 70 :javascript
71 71 $(function(){
72   - $('#project_owner_id').chosen();
73 72 new Projects();
74 73 })
75 74  
... ...
app/views/admin/projects/show.html.haml
... ... @@ -71,25 +71,11 @@
71 71 %th Project Access:
72 72  
73 73 %tr
74   - %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true
75   - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
  74 + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
  75 + %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
76 76  
77 77 %tr
78 78 %td= submit_tag 'Add', class: "btn primary"
79 79 %td
80 80 Read more about project permissions
81 81 %strong= link_to "here", help_permissions_path, class: "vlink"
82   -
83   -:css
84   - form select {
85   - width:150px;
86   - }
87   -
88   - #user_ids {
89   - width:300px;
90   - }
91   -
92   -:javascript
93   - $('select#user_ids').chosen();
94   - $('select#repo_access').chosen();
95   - $('select#project_access').chosen();
... ...
app/views/admin/resque/show.html.haml
1   -%h3 Resque
2   -%iframe{src: resque_url, width: 1168, height: 600, style: "border: none"}
  1 +%h3.page_title Resque
  2 +%br
  3 +.ui-box
  4 + %iframe{src: resque_url, width: '100%', height: 600, style: "border: none"}
... ...
app/views/admin/team_members/_form.html.haml
... ... @@ -8,20 +8,9 @@
8 8 .clearfix
9 9 %label Project Access:
10 10 .input
11   - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select"
  11 + = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
12 12  
13 13 %br
14 14 .actions
15 15 = f.submit 'Save', class: "btn primary"
16 16 = link_to 'Cancel', :back, class: "btn"
17   -
18   -:css
19   - form select {
20   - width:300px;
21   - }
22   -
23   -:javascript
24   - $('select#team_member_user_id').chosen();
25   - $('select#team_member_project_id').chosen();
26   - $('select#team_member_repo_access').chosen();
27   - $('select#team_member_project_access').chosen();
... ...
app/views/admin/users/show.html.haml
... ... @@ -68,8 +68,8 @@
68 68 %th Project Access:
69 69  
70 70 %tr
71   - %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true
72   - %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select"
  71 + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
  72 + %td= select_tag :project_access, options_for_select(Project.access_options), class: "project-access-select chosen span3"
73 73  
74 74 %tr
75 75 %td= submit_tag 'Add', class: "btn primary"
... ... @@ -97,17 +97,3 @@
97 97 %td= select_tag :tm_project_access, options_for_select(Project.access_options, tm.project_access), class: "medium project-access-select", disabled: :disabled
98 98 %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
99 99 %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
100   -
101   -:css
102   - form select {
103   - width:150px;
104   - }
105   -
106   - #project_ids {
107   - width:300px;
108   - }
109   -
110   -:javascript
111   - $('select#project_ids').chosen();
112   - $('select#repo_access').chosen();
113   - $('select#project_access').chosen();
... ...
app/views/commits/_commit_box.html.haml
... ... @@ -11,10 +11,10 @@
11 11 = link_to tree_project_ref_path(@project, @commit.id), class: "browse-button primary grouped" do
12 12 %strong Browse Code »
13 13 %h3.commit-title.page_title
14   - = gfm @commit.title
  14 + = gfm escape_once(@commit.title)
15 15 - if @commit.description.present?
16 16 %pre.commit-description
17   - = gfm @commit.description
  17 + = gfm escape_once(@commit.description)
18 18 .commit-info
19 19 .row
20 20 .span4
... ...
app/views/commits/_head.html.haml
1 1 %ul.nav.nav-tabs
2   - %li
3   - = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
4   - = select_tag "ref", grouped_options_refs, onchange: "$(this.form).trigger('submit');", class: "project-refs-select"
5   - = hidden_field_tag :destination, "commits"
6   -
  2 + %li= render partial: 'shared/ref_switcher', locals: {destination: 'commits'}
7 3 %li{class: "#{'active' if current_page?(project_commits_path(@project)) }"}
8 4 = link_to project_commits_path(@project) do
9 5 Commits
... ... @@ -20,14 +16,8 @@
20 16 Tags
21 17 %span.badge= @project.repo.tag_count
22 18  
23   -
24 19 - if current_page?(project_commits_path(@project)) && current_user.private_token
25 20 %li.right
26 21 %span.rss-icon
27 22 = link_to project_commits_path(@project, :atom, { private_token: current_user.private_token, ref: @ref }), title: "Feed" do
28 23 = image_tag "rss_ui.png", title: "feed"
29   -
30   -:javascript
31   - $(function(){
32   - $('.project-refs-select').chosen();
33   - });
... ...
app/views/commits/_text_file.html.haml
... ... @@ -13,14 +13,11 @@
13 13 %td.old_line
14 14 = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
15 15 - if @comments_allowed
16   - = link_to "", "#", class: "line_note_link", "line_code" => line_code, title: "Add note for this line"
  16 + = render "notes/per_line_note_link", line_code: line_code
17 17 %td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
18 18 %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} &nbsp;"
19 19  
20 20 - if @comments_allowed
21   - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at).reverse
  21 + - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at)
22 22 - unless comments.empty?
23   - - comments.each_with_index do |note, i|
24   - = render "notes/reply_button", line_code: line_code if i.zero?
25   - = render "notes/per_line_show", note: note
26   - - @line_notes.reject!{ |n| n == note }
  23 + = render "notes/per_line_notes_with_reply", notes: comments
... ...
app/views/commits/show.html.haml
1 1 = render "commits/commit_box"
2 2 = render "commits/diffs", diffs: @commit.diffs
3   -= render "notes/notes", tid: @commit.id, tt: "commit"
  3 += render "notes/notes_with_form", tid: @commit.id, tt: "commit"
4 4 = render "notes/per_line_form"
5 5  
6 6  
... ...
app/views/dashboard/index.html.haml
... ... @@ -31,13 +31,19 @@
31 31 %span= project_last_activity(project)
32 32 .bottom= paginate @projects, theme: "gitlab"
33 33  
34   - %hr
35 34 %div
36 35 %span.rss-icon
37 36 = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
38 37 = image_tag "rss_ui.png", title: "feed"
39 38 %strong News Feed
40 39  
  40 + %hr
  41 + .gitlab-promo
  42 + = link_to "Homepage", "http://gitlabhq.com"
  43 + = link_to "Blog", "http://blog.gitlabhq.com"
  44 + = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
  45 +
  46 +
41 47 - else
42 48 %h3.nothing_here_message There are no projects you have access to.
43 49 %br
... ...
app/views/devise/sessions/_new_ldap.html.haml
... ... @@ -15,7 +15,7 @@
15 15 $(function() {
16 16 $('#new_user').toggle();
17 17 });
18   - = form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
  18 += form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "login-box" }) do |f|
19 19 = f.text_field :email, :class => "text top", :placeholder => "Email"
20 20 = f.password_field :password, :class => "text bottom", :placeholder => "Password"
21 21 - if devise_mapping.rememberable?
... ...
app/views/devise/sessions/new.html.haml
... ... @@ -15,7 +15,7 @@
15 15 .right
16 16 = render :partial => "devise/shared/links"
17 17 - if devise_mapping.omniauthable?
  18 + %hr/
18 19 - resource_class.omniauth_providers.each do |provider|
19   - %hr/
20   - = link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider), :class => "btn primary"
21   - %br/
  20 + %span
  21 + = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
... ...
app/views/events/_commit.html.haml
... ... @@ -5,4 +5,4 @@
5 5 %strong.cdark= commit.author_name
6 6 &ndash;
7 7 = image_tag gravatar_icon(commit.author_email), class: "avatar", width: 16
8   - = gfm truncate(commit.title, length: 50) rescue "--broken encoding"
  8 + = gfm escape_once(truncate(commit.title, length: 50)) rescue "--broken encoding"
... ...
app/views/events/_event_last_push.html.haml
... ... @@ -2,7 +2,7 @@
2 2 .event_lp
3 3 %div
4 4 = image_tag gravatar_icon(event.author_email), class: "avatar"
5   - %span Your pushed to
  5 + %span Your pushed to
6 6 = event.ref_type
7 7 = link_to project_commits_path(event.project, ref: event.ref_name) do
8 8 %strong= truncate(event.ref_name, length: 28)
... ...
app/views/events/_event_membership_changed.html.haml
... ... @@ -2,7 +2,7 @@
2 2 %strong #{event.author_name}
3 3 %span.event_label{class: event.action_name}= event.action_name
4 4 project
5   -%strong= link_to event.project.name, event.project
  5 +%strong= link_to event.project_name, event.project
6 6 %span.cgray
7 7 = time_ago_in_words(event.created_at)
8 8 ago.
... ...
app/views/issues/_form.html.haml
... ... @@ -18,12 +18,12 @@
18 18 = f.label :assignee_id do
19 19 %i.icon-user
20 20 Assign to
21   - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" })
  21 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
22 22 .issue_milestone
23 23 = f.label :milestone_id do
24 24 %i.icon-time
25 25 Milestone
26   - .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" })
  26 + .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { include_blank: "Select milestone" }, {class: 'chosen'})
27 27  
28 28 .issue_description
29 29 .clearfix
... ...
app/views/issues/_show.html.haml
... ... @@ -4,7 +4,7 @@
4 4 = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
5 5 .right
6 6 - issue.labels.each do |label|
7   - %span.label.label-issue.grouped
  7 + %span.label.label-tag.grouped
8 8 %i.icon-tag
9 9 = label.name
10 10 - if issue.notes.any?
... ... @@ -34,5 +34,5 @@
34 34 - else
35 35 &nbsp;
36 36  
37   - - if issue.upvotes > 0
38   - %span.badge.badge-success= "+#{issue.upvotes}"
  37 + - if issue.votes_count > 0
  38 + = render 'votes/votes_inline', votable: issue
... ...
app/views/issues/edit.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - $('select#issue_milestone_id').chosen();
7   - });
8   -
... ...
app/views/issues/index.html.haml
... ... @@ -12,7 +12,7 @@
12 12 = form_tag search_project_issues_path(@project), method: :get, remote: true, id: "issue_search_form", class: :right do
13 13 = hidden_field_tag :project_id, @project.id, { id: 'project_id' }
14 14 = hidden_field_tag :status, params[:f]
15   - = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib' }
  15 + = search_field_tag :issue_search, nil, { placeholder: 'Search', class: 'issue_search span3 right neib search-text-input' }
16 16  
17 17 .clearfix
18 18  
... ...
app/views/issues/new.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - $('select#issue_milestone_id').chosen();
7   - });
8   -
... ...
app/views/issues/show.html.haml
... ... @@ -8,22 +8,22 @@
8 8 %span.right
9 9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
10 10 - if @issue.closed
11   - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small"
  11 + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped success"
12 12 - else
13   - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small", title: "Close Issue"
  13 + = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close Issue"
14 14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
15   - = link_to edit_project_issue_path(@project, @issue), class: "btn small" do
  15 + = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
16 16 %i.icon-edit
17 17 Edit
18 18  
19   - %br
20   - - if @issue.upvotes > 0
21   - .upvotes#upvotes= "+#{pluralize @issue.upvotes, 'upvote'}"
  19 +.right
  20 + .span3#votes= render 'votes/votes_block', votable: @issue
22 21  
23 22 .back_link
24 23 = link_to project_issues_path(@project) do
25 24 &larr; To issues list
26 25  
  26 +
27 27 .main_box
28 28 .top_box_content
29 29 %h4
... ... @@ -31,7 +31,7 @@
31 31 .alert-message.error.status_info Closed
32 32 - else
33 33 .alert-message.success.status_info Open
34   - = gfm @issue.title
  34 + = gfm escape_once(@issue.title)
35 35  
36 36 .middle_box_content
37 37 %cite.cgray Created by
... ... @@ -61,4 +61,4 @@
61 61 = markdown @issue.description
62 62  
63 63  
64   -.issue_notes#notes= render "notes/notes", tid: @issue.id, tt: "issue"
  64 +.issue_notes.voting_notes#notes= render "notes/notes_with_form", tid: @issue.id, tt: "issue"
... ...
app/views/labels/_label.html.haml
1 1 %li.wll
2   - %strong= label.name
  2 + %strong
  3 + %i.icon-tag
  4 + = label.name
3 5 .right
4   - %span= pluralize label.count, 'issue'
  6 + = link_to project_issues_path(label_name: label.name) do
  7 + %strong
  8 + = pluralize(label.count, 'issue')
  9 + = "»"
... ...
app/views/layouts/profile.html.haml
... ... @@ -9,20 +9,20 @@
9 9 %li.home{class: tab_class(:profile)}
10 10 = link_to "Profile", profile_path
11 11  
12   - %li{class: tab_class(:password)}
13   - = link_to "Password", profile_password_path
  12 + %li{class: tab_class(:account)}
  13 + = link_to "Account", profile_account_path
14 14  
15 15 %li{class: tab_class(:ssh_keys)}
16 16 = link_to keys_path do
17 17 SSH Keys
18 18 %span.count= current_user.keys.count
19 19  
20   - %li{class: tab_class(:token)}
21   - = link_to "Token", profile_token_path
22   -
23 20 %li{class: tab_class(:design)}
24 21 = link_to "Design", profile_design_path
25 22  
  23 + %li{class: tab_class(:history)}
  24 + = link_to "History", profile_history_path
  25 +
26 26  
27 27 .content
28 28 = yield
... ...
app/views/merge_requests/_form.html.haml
... ... @@ -16,7 +16,7 @@
16 16 .padded
17 17 = f.label :source_branch, "From", class: "control-label"
18 18 .controls
19   - = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
  19 + = f.select(:source_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
20 20 .mr_source_commit
21 21  
22 22 .span2
... ... @@ -28,7 +28,7 @@
28 28 .padded
29 29 = f.label :target_branch, "To", class: "control-label"
30 30 .controls
31   - = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, style: "width:250px")
  31 + = f.select(:target_branch, @project.heads.map(&:name), { include_blank: "Select branch" }, {class: 'chosen span3'})
32 32 .mr_target_commit
33 33  
34 34 %h4.cdark 2. Fill info
... ... @@ -43,7 +43,7 @@
43 43 = f.label :assignee_id do
44 44 %i.icon-user
45 45 Assign to
46   - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, style: "width:250px")
  46 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { include_blank: "Select user" }, {class: 'chosen span3'})
47 47  
48 48 .control-group
49 49  
... ... @@ -56,18 +56,12 @@
56 56 = link_to project_merge_request_path(@project, @merge_request), class: "btn cancel-btn" do
57 57 Cancel
58 58  
59   -
60   -
61 59 :javascript
62 60 $(function(){
63 61 disableButtonIfEmptyField("#merge_request_title", ".save-btn");
64   - $('select#merge_request_assignee_id').chosen();
65   - $('select#merge_request_source_branch').chosen();
66   - $('select#merge_request_target_branch').chosen();
67 62 var source_branch = $("#merge_request_source_branch");
68 63 var target_branch = $("#merge_request_target_branch");
69 64  
70   -
71 65 $.get("#{branch_from_project_merge_requests_path(@project)}", {ref: source_branch.val() });
72 66 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: target_branch.val() });
73 67  
... ... @@ -79,4 +73,3 @@
79 73 $.get("#{branch_to_project_merge_requests_path(@project)}", {ref: $(this).val() });
80 74 });
81 75 });
82   -
... ...
app/views/merge_requests/_merge_request.html.haml
... ... @@ -23,5 +23,6 @@
23 23 authored by #{merge_request.author_name}
24 24 = time_ago_in_words(merge_request.created_at)
25 25 ago
26   - - if merge_request.upvotes > 0
27   - %span.badge.badge-success= "+#{merge_request.upvotes}"
  26 +
  27 + - if merge_request.votes_count > 0
  28 + = render 'votes/votes_inline', votable: merge_request
... ...
app/views/merge_requests/_show.html.haml
... ... @@ -15,8 +15,8 @@
15 15 %i.icon-list-alt
16 16 Diff
17 17  
18   -.merge_request_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
19   - = render("notes/notes", tid: @merge_request.id, tt: "merge_request")
  18 +.merge_request_notes.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
  19 + = render("notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")
20 20 .merge-request-diffs
21 21 = render "merge_requests/show/diffs" if @diffs
22 22 .status
... ...
app/views/merge_requests/diffs.html.haml
1 1 = render "show"
2 2  
  3 +:javascript
  4 + $(function(){
  5 + PerLineNotes.init();
  6 + });
... ...
app/views/merge_requests/diffs.js.haml
1 1 :plain
2 2 $(".merge-request-diffs").html("#{escape_javascript(render(partial: "merge_requests/show/diffs"))}");
3 3  
  4 + $(function(){
  5 + PerLineNotes.init();
  6 + });
4 7  
... ...
app/views/merge_requests/show.js.haml
1 1 :plain
2   - $(".merge-request-notes").html("#{escape_javascript(render("notes/notes", tid: @merge_request.id, tt: "merge_request"))}");
  2 + $(".merge-request-notes").html("#{escape_javascript(render notes/notes_with_form", tid: @merge_request.id, tt: "merge_request")}");
... ...
app/views/merge_requests/show/_mr_box.html.haml
... ... @@ -5,7 +5,7 @@
5 5 .alert-message.error.status_info Closed
6 6 - else
7 7 .alert-message.success.status_info Open
8   - = gfm @merge_request.title
  8 + = gfm escape_once(@merge_request.title)
9 9  
10 10 .middle_box_content
11 11 %div
... ...
app/views/merge_requests/show/_mr_title.html.haml
... ... @@ -23,10 +23,8 @@
23 23 %i.icon-edit
24 24 Edit
25 25  
26   - %br
27   - - if @merge_request.upvotes > 0
28   - .upvotes#upvotes= "+#{pluralize @merge_request.upvotes, 'upvote'}"
29   -
  26 +.right
  27 + .span3#votes= render 'votes/votes_block', votable: @merge_request
30 28  
31 29 .back_link
32 30 = link_to project_merge_requests_path(@project) do
... ...
app/views/milestones/edit.html.haml
1 1 = render "form"
2   -
3   -:javascript
4   - $(function(){
5   - $('select#issue_assignee_id').chosen();
6   - });
7   -
... ...
app/views/milestones/show.html.haml
... ... @@ -21,7 +21,7 @@
21 21 .alert-message.error.status_info Closed
22 22 - else
23 23 .alert-message.success.status_info Open
24   - = gfm @milestone.title
  24 + = gfm escape_once(@milestone.title)
25 25 %small.right= @milestone.expires_at
26 26  
27 27 .middle_box_content
... ...
app/views/notes/_common_form.html.haml 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +.note-form-holder
  2 + = form_for [@project, @note], remote: "true", multipart: true do |f|
  3 + %h3.page_title Leave a comment
  4 + -if @note.errors.any?
  5 + .alert-message.block-message.error
  6 + - @note.errors.full_messages.each do |msg|
  7 + %div= msg
  8 +
  9 + = f.hidden_field :noteable_id
  10 + = f.hidden_field :noteable_type
  11 + = f.text_area :note, size: 255, class: 'note-text'
  12 + #preview-note.preview_note.hide
  13 + .hint
  14 + .right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  15 + .clearfix
  16 +
  17 + .row.note_advanced_opts
  18 + .span3
  19 + = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
  20 + = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
  21 + .span4.notify_opts
  22 + %h6.left Notify via email:
  23 + = label_tag :notify do
  24 + = check_box_tag :notify, 1, @note.noteable_type != "Commit"
  25 + %span Project team
  26 +
  27 + - if @note.notify_only_author?(current_user)
  28 + = label_tag :notify_author do
  29 + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
  30 + %span Commit author
  31 + .span5.attachments
  32 + %h6.left Attachment:
  33 + %span.file_name File name...
  34 +
  35 + .input.input_file
  36 + %a.file_upload.btn.small Upload File
  37 + = f.file_field :attachment, class: "input-file"
  38 + %span.hint Any file less than 10 MB
  39 +
... ...
app/views/notes/_create_common.js.haml
... ... @@ -1,12 +0,0 @@
1   -- if note.valid?
2   - :plain
3   - $(".note-form-holder .error").remove();
4   - $('.note-form-holder textarea').val("");
5   - $('.note-form-holder #preview-link').text('Preview');
6   - $('.note-form-holder #preview-note').hide();
7   - $('.note-form-holder').show();
8   - NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}");
9   -- else
10   - :plain
11   - $(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
12   -
app/views/notes/_create_common_note.js.haml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +- if note.valid?
  2 + :plain
  3 + $(".note-form-holder .error").remove();
  4 + $('.note-form-holder textarea').val("");
  5 + $('.note-form-holder #preview-link').text('Preview');
  6 + $('.note-form-holder #preview-note').hide();
  7 + $('.note-form-holder').show();
  8 + NoteList.appendNewNote(#{note.id}, "#{escape_javascript(render "notes/note", note: note)}");
  9 +
  10 +- else
  11 + :plain
  12 + $(".note-form-holder").replaceWith("#{escape_javascript(render 'form')}");
  13 +
... ...
app/views/notes/_create_line.js.haml
... ... @@ -1,8 +0,0 @@
1   -- if note.valid?
2   - :plain
3   - $(".per_line_form").hide();
4   - $('.line-note-form-holder textarea').val("");
5   - $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove();
6   - var trEl = $(".#{note.line_code}").parent();
7   - trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}");
8   - trEl.after("#{escape_javascript(render partial: "notes/reply_button", locals: {line_code: note.line_code})}");
app/views/notes/_create_per_line_note.js.haml 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +- if note.valid?
  2 + :plain
  3 + // hide and reset the form
  4 + $(".per_line_form").hide();
  5 + $('.line-note-form-holder textarea').val("");
  6 +
  7 + // find the reply button for this line
  8 + // (might not be there if this is the first note)
  9 + var trRpl = $("a.line_note_reply_link[data-line-code='#{note.line_code}']").closest("tr");
  10 + if (trRpl.size() == 0) {
  11 + // find the commented line ...
  12 + var trEl = $(".#{note.line_code}").parent();
  13 + // ... and insert the note and the reply button after it
  14 + trEl.after("#{escape_javascript(render "notes/per_line_reply_button", line_code: note.line_code)}");
  15 + trEl.after("#{escape_javascript(render "notes/per_line_note", note: note)}");
  16 + } else {
  17 + // instert new note before reply button
  18 + trRpl.before("#{escape_javascript(render "notes/per_line_note", note: note)}");
  19 + }
... ...
app/views/notes/_form.html.haml
... ... @@ -1,39 +0,0 @@
1   -.note-form-holder
2   - = form_for [@project, @note], remote: "true", multipart: true do |f|
3   - %h3.page_title Leave a comment
4   - -if @note.errors.any?
5   - .alert-message.block-message.error
6   - - @note.errors.full_messages.each do |msg|
7   - %div= msg
8   -
9   - = f.hidden_field :noteable_id
10   - = f.hidden_field :noteable_type
11   - = f.text_area :note, size: 255, class: 'note-text'
12   - #preview-note.preview_note.hide
13   - .hint
14   - .right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
15   - .clearfix
16   -
17   - .row.note_advanced_opts.hide
18   - .span3
19   - = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"
20   - = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'
21   - .span4.notify_opts
22   - %h6.left Notify via email:
23   - = label_tag :notify do
24   - = check_box_tag :notify, 1, @note.noteable_type != "Commit"
25   - %span Project team
26   -
27   - - if @note.notify_only_author?(current_user)
28   - = label_tag :notify_author do
29   - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
30   - %span Commit author
31   - .span5.attachments
32   - %h6.left Attachment:
33   - %span.file_name File name...
34   -
35   - .input.input_file
36   - %a.file_upload.btn.small Upload File
37   - = f.file_field :attachment, class: "input-file"
38   - %span.hint Any file less than 10 MB
39   -
app/views/notes/_load.js.haml
... ... @@ -1,17 +0,0 @@
1   -- unless @notes.blank?
2   - - if params[:last_id]
3   - :plain
4   - NoteList.replace("#{escape_javascript(render(partial: 'notes/notes_list'))}");
5   -
6   - - elsif params[:first_id]
7   - :plain
8   - NoteList.append(#{@notes.last.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}");
9   -
10   - - else
11   - :plain
12   - NoteList.setContent(#{@notes.last.id}, #{@notes.first.id}, "#{escape_javascript(render(partial: 'notes/notes_list'))}");
13   -
14   -- else
15   - - if params[:first_id]
16   - :plain
17   - NoteList.append(#{params[:first_id]}, "");
app/views/notes/_note.html.haml 0 → 100644
... ... @@ -0,0 +1,29 @@
  1 +%li{id: dom_id(note), class: "note #{note_vote_class(note)}"}
  2 + = image_tag gravatar_icon(note.author.email), class: "avatar s32"
  3 + %div.note-author
  4 + %strong= note.author_name
  5 + = link_to "##{dom_id(note)}", name: dom_id(note) do
  6 + %cite.cgray
  7 + = time_ago_in_words(note.updated_at)
  8 + ago
  9 + - if note.upvote?
  10 + %span.label.label-success
  11 + %i.icon-thumbs-up
  12 + \+1
  13 + - if note.downvote?
  14 + %span.label.label-error
  15 + %i.icon-thumbs-down
  16 + \-1
  17 + - if(note.author_id == current_user.id) || can?(current_user, :admin_note, @project)
  18 + = link_to [@project, note], confirm: 'Are you sure?', method: :delete, remote: true, class: "cred delete-note btn very_small" do
  19 + %i.icon-trash
  20 + Remove
  21 +
  22 + %div.note-title
  23 + = preserve do
  24 + = markdown(note.note)
  25 + - if note.attachment.url
  26 + .right
  27 + %div.file
  28 + = link_to note.attachment_identifier, note.attachment.url, target: "_blank"
  29 + .clear
... ...
app/views/notes/_notes.html.haml
1   -- if can? current_user, :write_note, @project
2   - = render "notes/form"
3   -.clear
4   -%hr
5   -%ul#new_notes_list
6   -%ul#notes-list
7   -.status
  1 +- @notes.each do |note|
  2 + - next unless note.author
  3 + = render "note", note: note
8 4  
9   -
10   -:javascript
11   - $(function(){
12   - NoteList.init("#{tid}", "#{tt}", "#{project_notes_path(@project)}");
13   - });
... ...
app/views/notes/_notes_list.html.haml
... ... @@ -1,4 +0,0 @@
1   -- @notes.each do |note|
2   - - next unless note.author
3   - = render partial: "notes/show", locals: {note: note}
4   -