Commit 4a6596af274c01036aaf9f49a5b38cd678716873

Authored by randx
1 parent 92137b7b

Fixed bunch of js bugs with comments. Also added development tips

@@ -39,5 +39,6 @@ Email @@ -39,5 +39,6 @@ Email
39 39
40 ## Contribute 40 ## Contribute
41 41
  42 +[Development Tips](https://github.com/gitlabhq/gitlabhq/blob/master/doc/development.md)
42 Want to help - send a pull request. 43 Want to help - send a pull request.
43 We'll accept good pull requests. 44 We'll accept good pull requests.
app/assets/javascripts/application.js
@@ -128,3 +128,23 @@ function showDiff(link) { @@ -128,3 +128,23 @@ function showDiff(link) {
128 function ajaxGet(url) { 128 function ajaxGet(url) {
129 $.ajax({type: "GET", url: url, dataType: "script"}); 129 $.ajax({type: "GET", url: url, dataType: "script"});
130 } 130 }
  131 +
  132 +/**
  133 + * Disable button if text field is empty
  134 + */
  135 +function disableButtonIfEmtpyField(field_selector, button_selector) {
  136 + field = $(field_selector);
  137 + if(field.val() == "") {
  138 + field.closest("form").find(button_selector).attr("disabled", "disabled").addClass("disabled");
  139 + }
  140 +
  141 + field.on('keyup', function(){
  142 + var field = $(this);
  143 + var closest_submit = field.closest("form").find(button_selector);
  144 + if(field.val() == "") {
  145 + closest_submit.attr("disabled", "disabled").addClass("disabled");
  146 + } else {
  147 + closest_submit.removeAttr("disabled").removeClass("disabled");
  148 + }
  149 + })
  150 +}
app/assets/javascripts/note.js
1 var NoteList = { 1 var NoteList = {
2 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_note').on('keyup', function(){  
28 - var field = $(this);  
29 - var closest_submit = field.closest("form").find(".submit_note");  
30 - if(field.val() == "") {  
31 - closest_submit.attr("disabled", "disabled").addClass("disabled");  
32 - } else {  
33 - closest_submit.removeAttr("disabled").removeClass("disabled");  
34 - }  
35 - })  
36 -  
37 - $("#new_note").live("ajax:before", function(){  
38 - $(".submit_note").attr("disabled", "disabled");  
39 - })  
40 -  
41 - $("#new_note").live("ajax:complete", function(){  
42 - $(".submit_note").removeAttr("disabled");  
43 - })  
44 -  
45 - $("#note_note").live("focus", function(){  
46 - $(this).css("height", "80px");  
47 - $('.note_advanced_opts').show();  
48 - if($(this).val() == "") {  
49 - $(this).closest("form").find(".submit_note").attr("disabled", "disabled").addClass("disabled");  
50 - }  
51 - });  
52 -  
53 - $("#note_attachment").change(function(e){ 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").attr("disabled", "disabled");
  29 + })
  30 +
  31 + $(".note-form-holder").live("ajax:complete", function(){
  32 + $(".submit_note").removeAttr("disabled");
  33 + })
  34 +
  35 + disableButtonIfEmtpyField(".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){
54 var val = $('.input-file').val(); 43 var val = $('.input-file').val();
55 var filename = val.replace(/^.*[\\\/]/, ''); 44 var filename = val.replace(/^.*[\\\/]/, '');
56 $(".file_name").text(filename); 45 $(".file_name").text(filename);
57 - }); 46 + });
58 47
59 - }, 48 + },
60 49
61 50
62 -/**  
63 - * Load new notes to fresh list called 'new_notes_list':  
64 - * - Replace 'new_notes_list' with new list every n seconds  
65 - * - Append new notes to this list after submit  
66 - */ 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 + */
67 56
68 -initRefresh:  
69 - function() {  
70 - // init timer  
71 - var intNew = setInterval("NoteList.getNew()", 10000);  
72 - }, 57 + initRefresh:
  58 + function() {
  59 + // init timer
  60 + var intNew = setInterval("NoteList.getNew()", 10000);
  61 + },
73 62
74 -replace:  
75 - function(html) {  
76 - $("#new_notes_list").html(html);  
77 - }, 63 + replace:
  64 + function(html) {
  65 + $("#new_notes_list").html(html);
  66 + },
78 67
79 -prepend:  
80 - function(id, html) {  
81 - if(id != this.last_id) {  
82 - $("#new_notes_list").prepend(html);  
83 - }  
84 - }, 68 + prepend:
  69 + function(id, html) {
  70 + if(id != this.last_id) {
  71 + $("#new_notes_list").prepend(html);
  72 + }
  73 + },
85 74
86 -getNew:  
87 - function() {  
88 - // refersh notes list  
89 - $.ajax({  
90 - type: "GET", 75 + getNew:
  76 + function() {
  77 + // refersh notes list
  78 + $.ajax({
  79 + type: "GET",
91 url: this.notes_path, 80 url: this.notes_path,
92 data: "last_id=" + this.last_id + this.target_params, 81 data: "last_id=" + this.last_id + this.target_params,
93 dataType: "script"}); 82 dataType: "script"});
94 - }, 83 + },
95 84
96 -refresh:  
97 - function() {  
98 - // refersh notes list  
99 - $.ajax({  
100 - type: "GET", 85 + refresh:
  86 + function() {
  87 + // refersh notes list
  88 + $.ajax({
  89 + type: "GET",
101 url: this.notes_path, 90 url: this.notes_path,
102 data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params, 91 data: "first_id=" + this.first_id + "&last_id=" + this.last_id + this.target_params,
103 dataType: "script"}); 92 dataType: "script"});
104 - }, 93 + },
105 94
106 95
107 -/**  
108 - * Init load of notes:  
109 - * 1. Get content with ajax call  
110 - * 2. Set content of notes list with loaded one  
111 - */ 96 + /**
  97 + * Init load of notes:
  98 + * 1. Get content with ajax call
  99 + * 2. Set content of notes list with loaded one
  100 + */
112 101
113 102
114 -getContent:  
115 - function() {  
116 - $.ajax({  
117 - type: "GET", 103 + getContent:
  104 + function() {
  105 + $.ajax({
  106 + type: "GET",
118 url: this.notes_path, 107 url: this.notes_path,
119 data: "?" + this.target_params, 108 data: "?" + this.target_params,
120 complete: function(){ $('.status').removeClass("loading")}, 109 complete: function(){ $('.status').removeClass("loading")},
121 beforeSend: function() { $('.status').addClass("loading") }, 110 beforeSend: function() { $('.status').addClass("loading") },
122 dataType: "script"}); 111 dataType: "script"});
123 - }, 112 + },
124 113
125 -setContent:  
126 - function(fid, lid, html) { 114 + setContent:
  115 + function(fid, lid, html) {
127 this.last_id = lid; 116 this.last_id = lid;
128 this.first_id = fid; 117 this.first_id = fid;
129 $("#notes-list").html(html); 118 $("#notes-list").html(html);
130 119
131 // Init infinite scrolling 120 // Init infinite scrolling
132 this.initLoadMore(); 121 this.initLoadMore();
133 - },  
134 -  
135 -  
136 -/**  
137 - * Paging for old notes when scroll to bottom:  
138 - * 1. Init scroll events with 'initLoadMore'  
139 - * 2. Load onlder notes with 'getOld' method  
140 - * 3. append old notes to bottom of list with 'append'  
141 - *  
142 - */  
143 -  
144 -  
145 -getOld:  
146 - function() {  
147 - $('.loading').show();  
148 - $.ajax({  
149 - type: "GET",  
150 - url: this.notes_path,  
151 - data: "first_id=" + this.first_id + this.target_params,  
152 - complete: function(){ $('.status').removeClass("loading")},  
153 - beforeSend: function() { $('.status').addClass("loading") },  
154 - dataType: "script"});  
155 - },  
156 -  
157 -append:  
158 - function(id, html) {  
159 - if(this.first_id == id) {  
160 - this.disable = true;  
161 - } else {  
162 - this.first_id = id;  
163 - $("#notes-list").append(html);  
164 - }  
165 - },  
166 - 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 + },
167 153
168 -initLoadMore:  
169 - function() {  
170 - $(document).endlessScroll({  
171 - bottomPixels: 400, 154 + initLoadMore:
  155 + function() {
  156 + $(document).endlessScroll({
  157 + bottomPixels: 400,
172 fireDelay: 1000, 158 fireDelay: 1000,
173 fireOnce:true, 159 fireOnce:true,
174 ceaseFire: function() { 160 ceaseFire: function() {
@@ -177,6 +163,20 @@ initLoadMore: @@ -177,6 +163,20 @@ initLoadMore:
177 callback: function(i) { 163 callback: function(i) {
178 NoteList.getOld(); 164 NoteList.getOld();
179 } 165 }
180 - });  
181 - } 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 + disableButtonIfEmtpyField(".line-note-text", ".submit_inline_note");
  181 + }
182 } 182 }
app/assets/stylesheets/sections/notes.scss
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 } 30 }
31 31
32 #new_note { 32 #new_note {
33 - #note_note { 33 + .note-text {
34 height:25px; 34 height:25px;
35 } 35 }
36 .attach_holder { 36 .attach_holder {
app/views/commits/show.html.haml
@@ -5,12 +5,6 @@ @@ -5,12 +5,6 @@
5 5
6 6
7 :javascript 7 :javascript
8 - $(document).ready(function(){  
9 - $(".line_note_link, .line_note_reply_link").live("click", function(e) {  
10 - var form = $(".per_line_form");  
11 - $(this).parent().parent().after(form);  
12 - form.find("#note_line_code").val($(this).attr("line_code"));  
13 - form.show();  
14 - return false;  
15 - }); 8 + $(function(){
  9 + PerLineNotes.init();
16 }); 10 });
app/views/notes/_create_common.js.haml
1 - if note.valid? 1 - if note.valid?
2 :plain 2 :plain
3 - $("#new_note .error").remove();  
4 - $('#new_note textarea').val("");  
5 - $('#preview-link').text('Preview');  
6 - $('#preview-note').hide(); $('#note_note').show(); 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();
7 NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}"); 8 NoteList.prepend(#{note.id}, "#{escape_javascript(render partial: "notes/show", locals: {note: note})}");
8 - else 9 - else
9 :plain 10 :plain
10 - $("#new_note").replaceWith("#{escape_javascript(render('form'))}"); 11 + $(".note-form-holder").replaceWith("#{escape_javascript(render('form'))}");
11 12
app/views/notes/_create_line.js.haml
1 - if note.valid? 1 - if note.valid?
2 :plain 2 :plain
3 $(".per_line_form").hide(); 3 $(".per_line_form").hide();
4 - $('#new_note textarea').val(""); 4 + $('.line-note-form-holder textarea').val("");
5 $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove(); 5 $("a.line_note_reply_link[line_code='#{note.line_code}']").closest("tr").remove();
6 var trEl = $(".#{note.line_code}").parent(); 6 var trEl = $(".#{note.line_code}").parent();
7 trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}"); 7 trEl.after("#{escape_javascript(render partial: "notes/per_line_show", locals: {note: note})}");
app/views/notes/_form.html.haml
1 -= form_for [@project, @note], remote: "true", multipart: true do |f|  
2 - %h3.page_title Leave a comment  
3 - -if @note.errors.any?  
4 - .alert-message.block-message.error  
5 - - @note.errors.full_messages.each do |msg|  
6 - %div= msg 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
7 8
8 - = f.hidden_field :noteable_id  
9 - = f.hidden_field :noteable_type  
10 - = f.text_area :note, size: 255  
11 - #preview-note.preview_note.hide  
12 - .hint  
13 - .right Comments are parsed with #{link_to "Gitlab Flavored Markdown", help_markdown_path, target: '_blank'}.  
14 - .clearfix 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
15 16
16 - .row.note_advanced_opts.hide  
17 - .span3  
18 - = f.submit 'Add Comment', class: "btn success submit_note grouped", id: "submit_note"  
19 - = link_to 'Preview', preview_project_notes_path(@project), class: 'btn grouped', id: 'preview-link'  
20 - .span4.notify_opts  
21 - %h6.left Notify via email:  
22 - = label_tag :notify do  
23 - = check_box_tag :notify, 1, @note.noteable_type != "Commit"  
24 - %span Project team 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
25 26
26 - - if @note.notify_only_author?(current_user)  
27 - = label_tag :notify_author do  
28 - = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"  
29 - %span Commit author  
30 - .span5.attachments  
31 - %h6.left Attachment:  
32 - %span.file_name File name... 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...
33 34
34 - .input.input_file  
35 - %a.file_upload.btn.small Upload File  
36 - = f.file_field :attachment, class: "input-file"  
37 - %span.hint Any file less than 10 MB 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
38 39
app/views/notes/_per_line_form.html.haml
1 %table{style: "display:none;"} 1 %table{style: "display:none;"}
2 %tr.per_line_form 2 %tr.per_line_form
3 %td{colspan: 3 } 3 %td{colspan: 3 }
4 - = form_for [@project, @note], remote: "true", multipart: true do |f|  
5 - %h3.page_title Leave a note  
6 - %div.span10  
7 - -if @note.errors.any?  
8 - .alert-message.block-message.error  
9 - - @note.errors.full_messages.each do |msg|  
10 - %div= msg 4 + .line-note-form-holder
  5 + = form_for [@project, @note], remote: "true", multipart: true do |f|
  6 + %h3.page_title Leave a note
  7 + %div.span10
  8 + -if @note.errors.any?
  9 + .alert-message.block-message.error
  10 + - @note.errors.full_messages.each do |msg|
  11 + %div= msg
11 12
12 - = f.hidden_field :noteable_id  
13 - = f.hidden_field :noteable_type  
14 - = f.hidden_field :line_code  
15 - = f.text_area :note, size: 255  
16 - .note_actions  
17 - .buttons  
18 - = f.submit 'Add note', class: "btn primary submit_note", id: "submit_note"  
19 - = link_to "Cancel", "#", class: "btn hide-button"  
20 - .options  
21 - %h6.left Notify via email:  
22 - .labels  
23 - = label_tag :notify do  
24 - = check_box_tag :notify, 1, @note.noteable_type != "Commit"  
25 - %span Project team 13 + = f.hidden_field :noteable_id
  14 + = f.hidden_field :noteable_type
  15 + = f.hidden_field :line_code
  16 + = f.text_area :note, size: 255, class: 'line-note-text'
  17 + .note_actions
  18 + .buttons
  19 + = f.submit 'Add note', class: "btn primary submit_note submit_inline_note", id: "submit_note"
  20 + = link_to "Cancel", "#", class: "btn hide-button"
  21 + .options
  22 + %h6.left Notify via email:
  23 + .labels
  24 + = label_tag :notify do
  25 + = check_box_tag :notify, 1, @note.noteable_type != "Commit"
  26 + %span Project team
26 27
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 28 + - if @note.notify_only_author?(current_user)
  29 + = label_tag :notify_author do
  30 + = check_box_tag :notify_author, 1 , @note.noteable_type == "Commit"
  31 + %span Commit author
31 32
32 :javascript 33 :javascript
33 $(function(){ 34 $(function(){
doc/development.md 0 → 100644
@@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
  1 +## Development tips:
  2 +
  3 +### Start application in development mode
  4 +
  5 +#### 1. Via foreman
  6 +
  7 + bundle exec foreman -p 3000
  8 +
  9 +#### 2. Via gitlab cli
  10 +
  11 + ./gitlab start
  12 +
  13 +#### 3. Manually
  14 +
  15 + bundle exec rails s
  16 + bundle exec rake environment resque:work QUEUE=* VVERBOSE=1
  17 +
  18 +
  19 +### Run tests:
  20 +
  21 +#### 1. Packages
  22 +
  23 + # ubuntu
  24 + sudo apt-get install libqt4-dev libqtwebkit-dev
  25 + sudo apt-get install xvfb
  26 +
  27 + # Mac
  28 + brew install qt
  29 + brew install xvfb
  30 +
  31 +#### 2. DB & seeds
  32 +
  33 + bundle exec rake db:setup RAILS_ENV=test
  34 + bundle exec rake db:seed_fu RAILS_ENV=test
  35 +
  36 +### 3. Run Tests
  37 +
  38 + # All in one
  39 + bundle exec gitlab:test
  40 +
  41 + # Rspec
  42 + bundle exec rake spec
  43 +
  44 + # Cucumber
  45 + bundle exec rake cucumber
@@ -22,10 +22,11 @@ class GitlabCli @@ -22,10 +22,11 @@ class GitlabCli
22 case @mode 22 case @mode
23 when 'production'; 23 when 'production';
24 system(unicorn_start_cmd) 24 system(unicorn_start_cmd)
  25 + system(resque_start_cmd)
25 else 26 else
26 system(rails_start_cmd) 27 system(rails_start_cmd)
  28 + system(resque_dev_start_cmd)
27 end 29 end
28 - system(resque_start_cmd)  
29 end 30 end
30 31
31 def stop 32 def stop
@@ -57,6 +58,10 @@ class GitlabCli @@ -57,6 +58,10 @@ class GitlabCli
57 "kill -QUIT `cat #{pid}`" 58 "kill -QUIT `cat #{pid}`"
58 end 59 end
59 60
  61 + def resque_dev_start_cmd
  62 + "./resque_dev.sh > /dev/null 2>&1"
  63 + end
  64 +
60 def resque_start_cmd 65 def resque_start_cmd
61 "./resque.sh > /dev/null 2>&1" 66 "./resque.sh > /dev/null 2>&1"
62 end 67 end
1 -bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 1 +mkdir -p tmp/pids
  2 +bundle exec rake environment resque:work QUEUE=post_receive,mailer,system_hook VVERBOSE=1 PIDFILE=tmp/pids/resque_worker.pid RAILS_ENV=development BACKGROUND=yes