Commit c51ac0484f37b65b31cd83b816b151fc7d50cf05

Authored by Dmitriy Zaporozhets
2 parents 3083753c d859d080

Merge pull request #5962 from skv-headless/editing-preview

editing preview
@@ -83,6 +83,9 @@ gem "seed-fu" @@ -83,6 +83,9 @@ gem "seed-fu"
83 gem "redcarpet", "~> 2.2.2" 83 gem "redcarpet", "~> 2.2.2"
84 gem "github-markup" 84 gem "github-markup"
85 85
  86 +# Diffs
  87 +gem 'diffy', '~> 3.0.3'
  88 +
86 # Asciidoc to HTML 89 # Asciidoc to HTML
87 gem "asciidoctor" 90 gem "asciidoctor"
88 91
@@ -101,6 +101,7 @@ GEM @@ -101,6 +101,7 @@ GEM
101 devise-async (0.8.0) 101 devise-async (0.8.0)
102 devise (>= 2.2, < 3.2) 102 devise (>= 2.2, < 3.2)
103 diff-lcs (1.2.5) 103 diff-lcs (1.2.5)
  104 + diffy (3.0.3)
104 docile (1.1.1) 105 docile (1.1.1)
105 dotenv (0.9.0) 106 dotenv (0.9.0)
106 email_spec (1.5.0) 107 email_spec (1.5.0)
@@ -577,6 +578,7 @@ DEPENDENCIES @@ -577,6 +578,7 @@ DEPENDENCIES
577 default_value_for (~> 3.0.0) 578 default_value_for (~> 3.0.0)
578 devise (= 3.0.4) 579 devise (= 3.0.4)
579 devise-async (= 0.8.0) 580 devise-async (= 0.8.0)
  581 + diffy (~> 3.0.3)
580 email_spec 582 email_spec
581 email_validator (~> 1.4.0) 583 email_validator (~> 1.4.0)
582 enumerize 584 enumerize
app/assets/stylesheets/generic/files.scss
@@ -26,6 +26,10 @@ @@ -26,6 +26,10 @@
26 margin-top: -5px; 26 margin-top: -5px;
27 } 27 }
28 28
  29 + .left-options {
  30 + margin-top: -3px;
  31 + }
  32 +
29 .file_name { 33 .file_name {
30 color: $style_color; 34 color: $style_color;
31 font-size: 14px; 35 font-size: 14px;
app/controllers/projects/edit_tree_controller.rb
@@ -26,6 +26,18 @@ class Projects::EditTreeController &lt; Projects::BaseTreeController @@ -26,6 +26,18 @@ class Projects::EditTreeController &lt; Projects::BaseTreeController
26 end 26 end
27 end 27 end
28 28
  29 + def preview
  30 + @content = params[:content]
  31 + #FIXME workaround https://github.com/gitlabhq/gitlabhq/issues/5936
  32 + @content += "\n" if @blob.data.end_with?("\n")
  33 +
  34 + diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
  35 + include_diff_info: true)
  36 + @diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/))
  37 +
  38 + render layout: false
  39 + end
  40 +
29 private 41 private
30 42
31 def blob 43 def blob
app/helpers/commits_helper.rb
@@ -16,9 +16,10 @@ module CommitsHelper @@ -16,9 +16,10 @@ module CommitsHelper
16 end 16 end
17 17
18 def each_diff_line(diff, index) 18 def each_diff_line(diff, index)
19 - Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|  
20 - yield(full_line, type, line_code, line_new, line_old)  
21 - end 19 + Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
  20 + .each do |full_line, type, line_code, line_new, line_old|
  21 + yield(full_line, type, line_code, line_new, line_old)
  22 + end
22 end 23 end
23 24
24 def each_diff_line_near(diff, index, expected_line_code) 25 def each_diff_line_near(diff, index, expected_line_code)
app/helpers/tree_helper.rb
@@ -91,4 +91,12 @@ module TreeHelper @@ -91,4 +91,12 @@ module TreeHelper
91 def leave_edit_message 91 def leave_edit_message
92 "Leave edit mode?\nAll unsaved changes will be lost." 92 "Leave edit mode?\nAll unsaved changes will be lost."
93 end 93 end
  94 +
  95 + def editing_preview_title(filename)
  96 + if gitlab_markdown?(filename) || markup?(filename)
  97 + 'Preview'
  98 + else
  99 + 'Diff'
  100 + end
  101 + end
94 end 102 end
app/models/note.rb
@@ -184,9 +184,10 @@ class Note &lt; ActiveRecord::Base @@ -184,9 +184,10 @@ class Note &lt; ActiveRecord::Base
184 return @diff_line if @diff_line 184 return @diff_line if @diff_line
185 185
186 if diff 186 if diff
187 - Gitlab::DiffParser.new(diff).each do |full_line, type, line_code, line_new, line_old|  
188 - @diff_line = full_line if line_code == self.line_code  
189 - end 187 + Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path)
  188 + .each do |full_line, type, line_code, line_new, line_old|
  189 + @diff_line = full_line if line_code == self.line_code
  190 + end
190 end 191 end
191 192
192 @diff_line 193 @diff_line
app/views/projects/edit_tree/_diff.html.haml 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +%table.text-file
  2 + - each_diff_line(diff, 1) do |line, type, line_code, line_new, line_old, raw_line|
  3 + %tr.line_holder{ id: line_code, class: "#{type}" }
  4 + - if type == "match"
  5 + %td.old_line= "..."
  6 + %td.new_line= "..."
  7 + %td.line_content.matched= line
  8 + - else
  9 + %td.old_line
  10 + = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
  11 + %td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
  12 + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
  13 +
app/views/projects/edit_tree/preview.html.haml 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +.diff-file
  2 + .diff-content
  3 + - if gitlab_markdown?(@blob.name)
  4 + .file-content.wiki
  5 + = preserve do
  6 + = markdown(@content)
  7 + - elsif markup?(@blob.name)
  8 + .file-content.wiki
  9 + = raw GitHub::Markup.render(@blob.name, @content)
  10 + - else
  11 + .file-content.code
  12 + - unless @diff.empty?
  13 + %table.text-file
  14 + - @diff.each do |line, type, line_code, line_new, line_old, raw_line|
  15 + %tr.line_holder{ id: line_code, class: "#{type}" }
  16 + - if type == "match"
  17 + %td.old_line= "..."
  18 + %td.new_line= "..."
  19 + %td.line_content.matched= line
  20 + - else
  21 + %td.old_line
  22 + = link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
  23 + %td.new_line= link_to raw(type == "old" ? "&nbsp;" : line_new) , "##{line_code}", id: line_code
  24 + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line)
  25 + - else
  26 + %p.nothing_here_message No changes.
app/views/projects/edit_tree/show.html.haml
1 %h3.page-title Edit mode 1 %h3.page-title Edit mode
2 .file-editor 2 .file-editor
3 = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do 3 = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
4 - .file-holder 4 + .file-holder.file
5 .file-title 5 .file-title
  6 + .btn-group.js-edit-mode.left-options
  7 + = link_to 'Edit', '#editor', class: 'active hover btn btn-tiny'
  8 + = link_to editing_preview_title(@blob.name), '#preview', class: 'btn btn-tiny', 'data-preview-url' => preview_project_edit_tree_path(@project, @id)
6 %i.icon-file 9 %i.icon-file
7 %span.file_name 10 %span.file_name
8 = @path 11 = @path
@@ -13,7 +16,8 @@ @@ -13,7 +16,8 @@
13 .btn-group.tree-btn-group 16 .btn-group.tree-btn-group
14 = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } 17 = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
15 .file-content.code 18 .file-content.code
16 - %pre#editor= @blob.data 19 + %pre.js-edit-mode-pane#editor= @blob.data
  20 + .js-edit-mode-pane#preview.hide
17 21
18 .form-group.commit_message-group 22 .form-group.commit_message-group
19 = label_tag 'commit_message', class: "control-label" do 23 = label_tag 'commit_message', class: "control-label" do
@@ -45,3 +49,28 @@ @@ -45,3 +49,28 @@
45 $("#file-content").val(editor.getValue()); 49 $("#file-content").val(editor.getValue());
46 $(".file-editor form").submit(); 50 $(".file-editor form").submit();
47 }); 51 });
  52 +
  53 + var editModePanes = $('.js-edit-mode-pane'),
  54 + editModeLinks = $('.js-edit-mode a');
  55 +
  56 + editModeLinks.click(function(event) {
  57 + event.preventDefault();
  58 +
  59 + var currentLink = $(this),
  60 + paneId = currentLink.attr('href'),
  61 + currentPane = editModePanes.filter(paneId);
  62 +
  63 + editModeLinks.removeClass('active hover');
  64 + currentLink.addClass('active hover');
  65 + editModePanes.hide();
  66 +
  67 + if (paneId == '#preview') {
  68 + $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) {
  69 + currentPane.empty().append(response);
  70 + currentPane.fadeIn(200);
  71 + })
  72 + } else {
  73 + currentPane.fadeIn(200);
  74 + editor.focus()
  75 + }
  76 + })
config/routes.rb
@@ -187,7 +187,9 @@ Gitlab::Application.routes.draw do @@ -187,7 +187,9 @@ Gitlab::Application.routes.draw do
187 resources :blob, only: [:show, :destroy], constraints: {id: /.+/} 187 resources :blob, only: [:show, :destroy], constraints: {id: /.+/}
188 resources :raw, only: [:show], constraints: {id: /.+/} 188 resources :raw, only: [:show], constraints: {id: /.+/}
189 resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } 189 resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
190 - resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' 190 + resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
  191 + post :preview, on: :member
  192 + end
191 resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' 193 resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
192 resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} 194 resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
193 resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} 195 resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
@@ -323,7 +323,6 @@ ActiveRecord::Schema.define(version: 20140428105831) do @@ -323,7 +323,6 @@ ActiveRecord::Schema.define(version: 20140428105831) do
323 t.integer "notification_level", default: 1, null: false 323 t.integer "notification_level", default: 1, null: false
324 t.datetime "password_expires_at" 324 t.datetime "password_expires_at"
325 t.integer "created_by_id" 325 t.integer "created_by_id"
326 - t.datetime "last_credential_check_at"  
327 t.string "avatar" 326 t.string "avatar"
328 t.string "confirmation_token" 327 t.string "confirmation_token"
329 t.datetime "confirmed_at" 328 t.datetime "confirmed_at"
@@ -331,6 +330,7 @@ ActiveRecord::Schema.define(version: 20140428105831) do @@ -331,6 +330,7 @@ ActiveRecord::Schema.define(version: 20140428105831) do
331 t.string "unconfirmed_email" 330 t.string "unconfirmed_email"
332 t.boolean "hide_no_ssh_key", default: false 331 t.boolean "hide_no_ssh_key", default: false
333 t.string "website_url", default: "", null: false 332 t.string "website_url", default: "", null: false
  333 + t.datetime "last_credential_check_at"
334 end 334 end
335 335
336 add_index "users", ["admin"], name: "index_users_on_admin", using: :btree 336 add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
features/project/source/browse_files.feature
@@ -29,3 +29,13 @@ Feature: Project Browse files @@ -29,3 +29,13 @@ Feature: Project Browse files
29 Given I click on "Gemfile.lock" file in repo 29 Given I click on "Gemfile.lock" file in repo
30 And I click button "edit" 30 And I click button "edit"
31 Then I can edit code 31 Then I can edit code
  32 +
  33 + @javascript
  34 + Scenario: I can see editing preview
  35 + Given I click on "Gemfile.lock" file in repo
  36 + And I click button "edit"
  37 + And I edit code
  38 + And I click link "Diff"
  39 + Then I see diff
  40 +
  41 +
features/steps/project/browse_files.rb
@@ -41,6 +41,18 @@ class ProjectBrowseFiles &lt; Spinach::FeatureSteps @@ -41,6 +41,18 @@ class ProjectBrowseFiles &lt; Spinach::FeatureSteps
41 page.evaluate_script('editor.getValue()').should == "GitlabFileEditor" 41 page.evaluate_script('editor.getValue()').should == "GitlabFileEditor"
42 end 42 end
43 43
  44 + step 'I edit code' do
  45 + page.execute_script('editor.setValue("GitlabFileEditor")')
  46 + end
  47 +
  48 + step 'I click link "Diff"' do
  49 + click_link 'Diff'
  50 + end
  51 +
  52 + step 'I see diff' do
  53 + page.should have_css '.line_holder.new'
  54 + end
  55 +
44 step 'I click on "new file" link in repo' do 56 step 'I click on "new file" link in repo' do
45 click_link 'new-file-link' 57 click_link 'new-file-link'
46 end 58 end
lib/gitlab/diff_parser.rb
@@ -4,9 +4,9 @@ module Gitlab @@ -4,9 +4,9 @@ module Gitlab
4 4
5 attr_reader :lines, :new_path 5 attr_reader :lines, :new_path
6 6
7 - def initialize(diff)  
8 - @lines = diff.diff.lines.to_a  
9 - @new_path = diff.new_path 7 + def initialize(lines, new_path = '')
  8 + @lines = lines
  9 + @new_path = new_path
10 end 10 end
11 11
12 def each 12 def each
@@ -18,10 +18,7 @@ module Gitlab @@ -18,10 +18,7 @@ module Gitlab
18 lines_arr.each do |line| 18 lines_arr.each do |line|
19 raw_line = line.dup 19 raw_line = line.dup
20 20
21 - next if line.match(/^\-\-\- \/dev\/null/)  
22 - next if line.match(/^\+\+\+ \/dev\/null/)  
23 - next if line.match(/^\-\-\- a/)  
24 - next if line.match(/^\+\+\+ b/) 21 + next if filename?(line)
25 22
26 full_line = html_escape(line.gsub(/\n/, '')) 23 full_line = html_escape(line.gsub(/\n/, ''))
27 full_line = ::Gitlab::InlineDiff.replace_markers full_line 24 full_line = ::Gitlab::InlineDiff.replace_markers full_line
@@ -53,8 +50,17 @@ module Gitlab @@ -53,8 +50,17 @@ module Gitlab
53 end 50 end
54 end 51 end
55 52
  53 + def empty?
  54 + @lines.empty?
  55 + end
  56 +
56 private 57 private
57 58
  59 + def filename?(line)
  60 + line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b',
  61 + '--- /tmp/diffy', '+++ /tmp/diffy')
  62 + end
  63 +
58 def identification_type(line) 64 def identification_type(line)
59 if line[0] == "+" 65 if line[0] == "+"
60 "new" 66 "new"