Commit e6ce47291b3f08ebe18c2450fc4f21a2a3a2b8a9
Exists in
master
and in
4 other branches
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.
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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
app/assets/javascripts/application.js
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 | -} |
... | ... | @@ -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
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/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 < 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
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 < 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 < 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
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 < 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 < 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 | ... | ... |
... | ... | @@ -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 < 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
app/controllers/issues_controller.rb
... | ... | @@ -17,7 +17,7 @@ class IssuesController < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 < 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 | ... | ... |
... | ... | @@ -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/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
app/models/event.rb
... | ... | @@ -35,13 +35,21 @@ class Event < 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 < 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 < 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 < 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 < 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
app/models/merge_request.rb
app/models/note.rb
... | ... | @@ -36,7 +36,7 @@ class Note < 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 < 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
app/models/tree.rb
app/models/user.rb
... | ... | @@ -86,33 +86,20 @@ class User < 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
app/observers/project_observer.rb
... | ... | @@ -4,6 +4,18 @@ class ProjectObserver < 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 < 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
... | ... | @@ -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
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" ? " " : 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" ? " " : line_new) , "##{line_code}", id: line_code |
18 | 18 | %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line} " |
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
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 | – |
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 | |
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
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
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 | ← 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
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
app/views/merge_requests/diffs.js.haml
app/views/merge_requests/show.js.haml
app/views/merge_requests/show/_mr_box.html.haml
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
app/views/milestones/show.html.haml
... | ... | @@ -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 | - |
... | ... | @@ -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})}"); |
... | ... | @@ -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]}, ""); |
... | ... | @@ -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