Commit 6a85cdf1627629ecaa762fa60a7abdbd092cc20a

Authored by Earle Bunao & Neil Calabroso
Committed by erbunao
1 parent 696b9903

Implements drag and drop upload in creating issues

@@ -69,6 +69,9 @@ gem "haml-rails" @@ -69,6 +69,9 @@ gem "haml-rails"
69 # Files attachments 69 # Files attachments
70 gem "carrierwave" 70 gem "carrierwave"
71 71
  72 +# Drag and Drop UI
  73 +gem 'dropzonejs-rails'
  74 +
72 # for aws storage 75 # for aws storage
73 gem "fog", "~> 1.14", group: :aws 76 gem "fog", "~> 1.14", group: :aws
74 gem "unf", group: :aws 77 gem "unf", group: :aws
@@ -103,6 +103,8 @@ GEM @@ -103,6 +103,8 @@ GEM
103 diffy (3.0.3) 103 diffy (3.0.3)
104 docile (1.1.1) 104 docile (1.1.1)
105 dotenv (0.9.0) 105 dotenv (0.9.0)
  106 + dropzonejs-rails (0.4.14)
  107 + rails (> 3.1)
106 email_spec (1.5.0) 108 email_spec (1.5.0)
107 launchy (~> 2.1) 109 launchy (~> 2.1)
108 mail (~> 2.2) 110 mail (~> 2.2)
@@ -579,6 +581,7 @@ DEPENDENCIES @@ -579,6 +581,7 @@ DEPENDENCIES
579 devise (= 3.0.4) 581 devise (= 3.0.4)
580 devise-async (= 0.8.0) 582 devise-async (= 0.8.0)
581 diffy (~> 3.0.3) 583 diffy (~> 3.0.3)
  584 + dropzonejs-rails
582 email_spec 585 email_spec
583 email_validator (~> 1.4.0) 586 email_validator (~> 1.4.0)
584 enumerize 587 enumerize
app/assets/javascripts/application.js.coffee
@@ -29,6 +29,7 @@ @@ -29,6 +29,7 @@
29 #= require underscore 29 #= require underscore
30 #= require nprogress 30 #= require nprogress
31 #= require nprogress-turbolinks 31 #= require nprogress-turbolinks
  32 +#= require dropzone
32 #= require_tree . 33 #= require_tree .
33 34
34 window.slugify = (text) -> 35 window.slugify = (text) ->
app/assets/javascripts/behaviors/toggler_behavior.coffee
1 $ -> 1 $ ->
2 $("body").on "click", ".js-toggler-target", -> 2 $("body").on "click", ".js-toggler-target", ->
3 - container = $(@).closest(".js-toggler-container") 3 + container = $(".notes-container")
4 container.toggleClass("on") 4 container.toggleClass("on")
5 5
6 # Toggle button. Show/hide content inside parent container. 6 # Toggle button. Show/hide content inside parent container.
app/assets/javascripts/markdown_area.js.coffee 0 → 100644
@@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
  1 +formatLink = (str) ->
  2 + "![" + str.alt + "](" + str.url + ")"
  3 +
  4 +$(document).ready ->
  5 + alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
  6 + alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
  7 + divHover = "<div class=\"div-dropzone-hover\"></div>"
  8 + divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
  9 + divAlert = "<div class=\"" + alertClass + "\"></div>"
  10 + iconPicture = "<i class=\"icon-picture div-dropzone-icon\"></i>"
  11 + iconSpinner = "<i class=\"icon-spinner icon-spin div-dropzone-icon\"></i>"
  12 + btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
  13 + project_image_path_upload = window.project_image_path_upload or null
  14 +
  15 + $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
  16 +
  17 + $(".div-dropzone").parent().addClass "div-dropzone-wrapper"
  18 +
  19 + $(".div-dropzone").append divHover
  20 + $(".div-dropzone-hover").append iconPicture
  21 + $(".div-dropzone").append divSpinner
  22 + $(".div-dropzone-spinner").append iconSpinner
  23 +
  24 +
  25 + dropzone = $(".div-dropzone").dropzone(
  26 + url: project_image_path_upload
  27 + dictDefaultMessage: ""
  28 + clickable: true
  29 + paramName: "markdown_img"
  30 + maxFilesize: 10
  31 + uploadMultiple: false
  32 + acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
  33 + headers:
  34 + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
  35 +
  36 + previewContainer: false
  37 +
  38 + processing: ->
  39 + $(".div-dropzone-alert").alert "close"
  40 +
  41 + dragover: ->
  42 + $(".div-dropzone > textarea").addClass "div-dropzone-focus"
  43 + $(".div-dropzone-hover").css "opacity", 0.7
  44 + return
  45 +
  46 + dragleave: ->
  47 + $(".div-dropzone > textarea").removeClass "div-dropzone-focus"
  48 + $(".div-dropzone-hover").css "opacity", 0
  49 + return
  50 +
  51 + drop: ->
  52 + $(".div-dropzone > textarea").removeClass "div-dropzone-focus"
  53 + $(".div-dropzone-hover").css "opacity", 0
  54 + $(".div-dropzone > textarea").focus()
  55 + return
  56 +
  57 + success: (header, response) ->
  58 + child = $(dropzone[0]).children("textarea")
  59 + $(child).val $(child).val() + formatLink(response.link) + "\n"
  60 + return
  61 +
  62 + error: (temp, errorMessage) ->
  63 + checkIfMsgExists = $(".error-alert").children().length
  64 + if checkIfMsgExists is 0
  65 + $(".error-alert").append divAlert
  66 + $(".div-dropzone-alert").append btnAlert + errorMessage
  67 + return
  68 +
  69 + sending: ->
  70 + $(".div-dropzone-spinner").css "opacity", 0.7
  71 + return
  72 +
  73 + complete: ->
  74 + $(".dz-preview").remove()
  75 + $(".markdown-area").trigger "input"
  76 + $(".div-dropzone-spinner").css "opacity", 0
  77 + return
  78 + )
  79 +
  80 + $(".markdown-selector").click (e) ->
  81 + e.preventDefault()
  82 + $(".div-dropzone").click()
  83 + return
  84 +
  85 + return
0 \ No newline at end of file 86 \ No newline at end of file
app/assets/stylesheets/application.scss
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 *= require_self 10 *= require_self
11 *= require nprogress 11 *= require nprogress
12 *= require nprogress-bootstrap 12 *= require nprogress-bootstrap
  13 + *= require dropzone/basic
13 */ 14 */
14 15
15 @import "main/*"; 16 @import "main/*";
app/assets/stylesheets/behaviors.scss
@@ -5,10 +5,19 @@ @@ -5,10 +5,19 @@
5 .js-details-container.open .content { display: block; } 5 .js-details-container.open .content { display: block; }
6 .js-details-container.open .content.hide { display: none; } 6 .js-details-container.open .content.hide { display: none; }
7 7
8 -  
9 // Toggler 8 // Toggler
10 //-------- 9 //--------
11 -.js-toggler-container .turn-on { display: inherit; } 10 +.write-preview-btn .turn-on { display: inherit; }
  11 +.write-preview-btn .turn-off { display: none; }
  12 +
12 .js-toggler-container .turn-off { display: none; } 13 .js-toggler-container .turn-off { display: none; }
13 .js-toggler-container.on .turn-on { display: none; } 14 .js-toggler-container.on .turn-on { display: none; }
14 .js-toggler-container.on .turn-off { display: inherit; } 15 .js-toggler-container.on .turn-off { display: inherit; }
  16 +
  17 +.js-toggler-container.on ~ .note-form-actions {
  18 + .write-preview-btn .turn-on { display: none; }
  19 +}
  20 +
  21 +.js-toggler-container.on ~ .note-form-actions {
  22 + .write-preview-btn .turn-off { display: inherit; }
  23 +}
app/assets/stylesheets/generic/markdown_area.scss 0 → 100644
@@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
  1 +.div-dropzone-wrapper {
  2 + .div-dropzone {
  3 + position: relative;
  4 + padding: 0;
  5 + border: 0;
  6 + margin-bottom: 5px;
  7 +
  8 + .div-dropzone-focus {
  9 + border-color: #66afe9 !important;
  10 + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
  11 + outline: 0 !important;
  12 + }
  13 +
  14 + .div-dropzone-hover {
  15 + position: absolute;
  16 + top: 50%;
  17 + left: 50%;
  18 + margin-top: -0.5em;
  19 + margin-left: -0.6em;
  20 + opacity: 0;
  21 + font-size: 50px;
  22 + transition: opacity 200ms ease-in-out;
  23 + }
  24 +
  25 + .div-dropzone-spinner {
  26 + position: absolute;
  27 + top: 100%;
  28 + left: 100%;
  29 + margin-top: -1.1em;
  30 + margin-left: -1.1em;
  31 + opacity: 0;
  32 + font-size: 30px;
  33 + transition: opacity 200ms ease-in-out;
  34 + }
  35 +
  36 + .div-dropzone-icon {
  37 + display: block;
  38 + text-align: center;
  39 + font-size: inherit;
  40 + }
  41 +
  42 + .dz-preview {
  43 + display: none;
  44 + }
  45 + }
  46 +
  47 + .hint {
  48 + float: left;
  49 + padding: 0;
  50 + margin: 0;
  51 + }
  52 +}
  53 +
  54 +.div-dropzone-alert {
  55 + margin-top: 5px;
  56 + margin-bottom: 0;
  57 + transition: opacity 200ms ease-in-out;
  58 +}
app/assets/stylesheets/sections/notes.scss
@@ -272,21 +272,16 @@ ul.notes { @@ -272,21 +272,16 @@ ul.notes {
272 margin-bottom: 0; 272 margin-bottom: 0;
273 } 273 }
274 .note_text_and_preview { 274 .note_text_and_preview {
275 - // makes the "absolute" position for links relative to this  
276 - position: relative;  
277 -  
278 - // preview/edit buttons  
279 - > a {  
280 - position: absolute;  
281 - right: 5px;  
282 - bottom: -60px;  
283 - }  
284 .note_preview { 275 .note_preview {
285 background: #f5f5f5; 276 background: #f5f5f5;
286 border: 1px solid #ddd; 277 border: 1px solid #ddd;
287 @include border-radius(4px); 278 @include border-radius(4px);
288 min-height: 80px; 279 min-height: 80px;
289 padding: 4px 6px; 280 padding: 4px 6px;
  281 +
  282 + > p {
  283 + overflow-x: auto;
  284 + }
290 } 285 }
291 .note_text { 286 .note_text {
292 border: 1px solid #DDD; 287 border: 1px solid #DDD;
@@ -310,7 +305,6 @@ ul.notes { @@ -310,7 +305,6 @@ ul.notes {
310 float: none; 305 float: none;
311 } 306 }
312 307
313 -  
314 .common-note-form { 308 .common-note-form {
315 margin: 0; 309 margin: 0;
316 background: #F9F9F9; 310 background: #F9F9F9;
@@ -318,7 +312,6 @@ ul.notes { @@ -318,7 +312,6 @@ ul.notes {
318 border: 1px solid #DDD; 312 border: 1px solid #DDD;
319 } 313 }
320 314
321 -  
322 .note-form-actions { 315 .note-form-actions {
323 background: #F9F9F9; 316 background: #F9F9F9;
324 height: 45px; 317 height: 45px;
@@ -333,6 +326,18 @@ ul.notes { @@ -333,6 +326,18 @@ ul.notes {
333 .js-notify-commit-author { 326 .js-notify-commit-author {
334 float: left; 327 float: left;
335 } 328 }
  329 +
  330 + .write-preview-btn {
  331 + // makes the "absolute" position for links relative to this
  332 + position: relative;
  333 +
  334 + // preview/edit buttons
  335 + > a {
  336 + position: absolute;
  337 + right: 5px;
  338 + top: 8px;
  339 + }
  340 + }
336 } 341 }
337 342
338 .note-edit-form { 343 .note-edit-form {
@@ -367,3 +372,8 @@ ul.notes { @@ -367,3 +372,8 @@ ul.notes {
367 .parallel-comment { 372 .parallel-comment {
368 padding: 6px; 373 padding: 6px;
369 } 374 }
  375 +
  376 +.error-alert > .alert {
  377 + margin-top: 5px;
  378 + margin-bottom: 5px;
  379 +}
app/controllers/files_controller.rb
@@ -14,4 +14,3 @@ class FilesController &lt; ApplicationController @@ -14,4 +14,3 @@ class FilesController &lt; ApplicationController
14 end 14 end
15 end 15 end
16 end 16 end
17 -  
app/controllers/projects/issues_controller.rb
@@ -69,7 +69,9 @@ class Projects::IssuesController &lt; Projects::ApplicationController @@ -69,7 +69,9 @@ class Projects::IssuesController &lt; Projects::ApplicationController
69 render :new 69 render :new
70 end 70 end
71 end 71 end
72 - format.js 72 + format.js do |format|
  73 + @link = @issue.attachment.url.to_js
  74 + end
73 end 75 end
74 end 76 end
75 77
app/controllers/projects_controller.rb
@@ -162,8 +162,28 @@ class ProjectsController &lt; ApplicationController @@ -162,8 +162,28 @@ class ProjectsController &lt; ApplicationController
162 end 162 end
163 end 163 end
164 164
  165 + def upload_image
  166 + uploader = FileUploader.new('uploads', upload_path, accepted_images)
  167 + alt = params['markdown_img'].original_filename
  168 + uploader.store!(params['markdown_img'])
  169 + link = { 'alt' => File.basename(alt, '.*'),
  170 + 'url' => File.join(root_url, uploader.url) }
  171 + respond_to do |format|
  172 + format.json { render json: { link: link } }
  173 + end
  174 + end
  175 +
165 private 176 private
166 177
  178 + def upload_path
  179 + base_dir = FileUploader.generate_dir
  180 + File.join(repository.path_with_namespace, base_dir)
  181 + end
  182 +
  183 + def accepted_images
  184 + %w(png jpg jpeg gif)
  185 + end
  186 +
167 def set_title 187 def set_title
168 @title = 'New Project' 188 @title = 'New Project'
169 end 189 end
app/models/issue.rb
@@ -15,8 +15,12 @@ @@ -15,8 +15,12 @@
15 # milestone_id :integer 15 # milestone_id :integer
16 # state :string(255) 16 # state :string(255)
17 # iid :integer 17 # iid :integer
  18 +# attachment :string(255)
18 # 19 #
19 20
  21 +require 'carrierwave/orm/activerecord'
  22 +require 'file_size_validator'
  23 +
20 class Issue < ActiveRecord::Base 24 class Issue < ActiveRecord::Base
21 include Issuable 25 include Issuable
22 include InternalId 26 include InternalId
app/uploaders/file_uploader.rb 0 → 100644
@@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
  1 +# encoding: utf-8
  2 +class FileUploader < CarrierWave::Uploader::Base
  3 + storage :file
  4 +
  5 + def initialize(base_dir, path = '', allowed_extensions = nil)
  6 + @base_dir = base_dir
  7 + @path = path
  8 + @allowed_extensions = allowed_extensions
  9 + end
  10 +
  11 + def base_dir
  12 + @base_dir
  13 + end
  14 +
  15 + def store_dir
  16 + File.join(@base_dir, @path)
  17 + end
  18 +
  19 + def cache_dir
  20 + File.join(@base_dir, 'tmp', @path)
  21 + end
  22 +
  23 + def extension_white_list
  24 + @allowed_extensions
  25 + end
  26 +
  27 + def store!(file)
  28 + file.original_filename = self.class.generate_filename(file)
  29 + super
  30 + end
  31 +
  32 + def self.generate_filename(file)
  33 + original_filename = File.basename(file.original_filename, '.*')
  34 + extension = File.extname(file.original_filename)
  35 + new_filename = Digest::MD5.hexdigest(original_filename) + extension
  36 + end
  37 +
  38 + def self.generate_dir
  39 + SecureRandom.hex(5)
  40 + end
  41 +end
app/views/layouts/admin.html.haml
@@ -10,3 +10,4 @@ @@ -10,3 +10,4 @@
10 10
11 .container 11 .container
12 .content= yield 12 .content= yield
  13 + = yield :embedded_scripts
13 \ No newline at end of file 14 \ No newline at end of file
app/views/layouts/projects.html.haml
@@ -14,3 +14,4 @@ @@ -14,3 +14,4 @@
14 14
15 .container 15 .container
16 .content= yield 16 .content= yield
  17 + = yield :embedded_scripts
17 \ No newline at end of file 18 \ No newline at end of file
app/views/projects/issues/_form.html.haml
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) 5 - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
6 .alert.alert-info.col-sm-10.col-sm-offset-2 6 .alert.alert-info.col-sm-10.col-sm-offset-2
7 ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe 7 ="Please review the <strong>#{link_to "guidelines for contribution", contribution_guide_url}</strong> to this repository.".html_safe
  8 +
8 = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f| 9 = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f|
9 -if @issue.errors.any? 10 -if @issue.errors.any?
10 .alert.alert-danger 11 .alert.alert-danger
@@ -19,8 +20,12 @@ @@ -19,8 +20,12 @@
19 .form-group 20 .form-group
20 = f.label :description, 'Description', class: 'control-label' 21 = f.label :description, 'Description', class: 'control-label'
21 .col-sm-10 22 .col-sm-10
22 - = f.text_area :description, class: "form-control js-gfm-input", rows: 14  
23 - %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 23 + = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14
  24 + .col-sm-12.hint
  25 + .pull-left Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  26 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
  27 + .clearfix
  28 + .error-alert
24 %hr 29 %hr
25 .form-group 30 .form-group
26 .issue-assignee 31 .issue-assignee
@@ -57,9 +62,6 @@ @@ -57,9 +62,6 @@
57 - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue) 62 - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue)
58 = link_to "Cancel", cancel_path, class: 'btn btn-cancel' 63 = link_to "Cancel", cancel_path, class: 'btn btn-cancel'
59 64
60 -  
61 -  
62 -  
63 :javascript 65 :javascript
64 $("#issue_label_list") 66 $("#issue_label_list")
65 .bind( "keydown", function( event ) { 67 .bind( "keydown", function( event ) {
@@ -94,3 +96,5 @@ @@ -94,3 +96,5 @@
94 $('#issue_assignee_id').val("#{current_user.id}").trigger("change"); 96 $('#issue_assignee_id').val("#{current_user.id}").trigger("change");
95 e.preventDefault(); 97 e.preventDefault();
96 }); 98 });
  99 +
  100 + window.project_image_path_upload = "#{upload_image_project_path @project}";
app/views/projects/issues/show.html.haml
@@ -73,4 +73,4 @@ @@ -73,4 +73,4 @@
73 = label.name 73 = label.name
74 &nbsp; 74 &nbsp;
75 75
76 -.voting_notes#notes= render "projects/notes/notes_with_form" 76 -.voting_notes#notes= render "projects/notes/notes_with_form"
  77 +.voting_notes#notes= render "projects/notes/notes_with_form"
77 \ No newline at end of file 78 \ No newline at end of file
app/views/projects/merge_requests/_form.html.haml
@@ -22,8 +22,12 @@ @@ -22,8 +22,12 @@
22 .form-group 22 .form-group
23 = f.label :description, "Description", class: 'control-label' 23 = f.label :description, "Description", class: 'control-label'
24 .col-sm-10 24 .col-sm-10
25 - = f.text_area :description, class: "form-control js-gfm-input", rows: 14  
26 - %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 25 + = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14
  26 + .col-sm-12.hint
  27 + .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  28 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
  29 + .clearfix
  30 + .error-alert
27 %hr 31 %hr
28 .form-group 32 .form-group
29 .issue-assignee 33 .issue-assignee
@@ -98,3 +102,5 @@ @@ -98,3 +102,5 @@
98 return false; 102 return false;
99 } 103 }
100 }); 104 });
  105 +
  106 + window.project_image_path_upload = "#{upload_image_project_path @project}";
app/views/projects/merge_requests/_new_submit.html.haml
@@ -23,8 +23,12 @@ @@ -23,8 +23,12 @@
23 .form-group 23 .form-group
24 .light 24 .light
25 = f.label :description, "Description" 25 = f.label :description, "Description"
26 - = f.text_area :description, class: "form-control js-gfm-input", rows: 10  
27 - %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 26 + = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10
  27 + .col-sm-12.hint
  28 + .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  29 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
  30 + .clearfix
  31 + .error-alert
28 .form-group 32 .form-group
29 .issue-assignee 33 .issue-assignee
30 = f.label :assignee_id do 34 = f.label :assignee_id do
@@ -80,3 +84,5 @@ @@ -80,3 +84,5 @@
80 $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); 84 $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
81 e.preventDefault(); 85 e.preventDefault();
82 }); 86 });
  87 +
  88 + window.project_image_path_upload = "#{upload_image_project_path @project}";
app/views/projects/milestones/_form.html.haml
@@ -21,8 +21,12 @@ @@ -21,8 +21,12 @@
21 .form-group 21 .form-group
22 = f.label :description, "Description", class: "control-label" 22 = f.label :description, "Description", class: "control-label"
23 .col-sm-10 23 .col-sm-10
24 - = f.text_area :description, maxlength: 2000, class: "form-control", rows: 10  
25 - %p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 24 + = f.text_area :description, maxlength: 2000, class: "form-control markdown-area", rows: 10
  25 + .hint
  26 + .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  27 + .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
  28 + .clearfix
  29 + .error-alert
26 .col-md-6 30 .col-md-6
27 .form-group 31 .form-group
28 = f.label :due_date, "Due Date", class: "control-label" 32 = f.label :due_date, "Due Date", class: "control-label"
@@ -45,3 +49,5 @@ @@ -45,3 +49,5 @@
45 dateFormat: "yy-mm-dd", 49 dateFormat: "yy-mm-dd",
46 onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } 50 onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
47 }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); 51 }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
  52 +
  53 + window.project_image_path_upload = "#{upload_image_project_path @project}";
app/views/projects/notes/_form.html.haml
@@ -5,20 +5,15 @@ @@ -5,20 +5,15 @@
5 = f.hidden_field :noteable_id 5 = f.hidden_field :noteable_id
6 = f.hidden_field :noteable_type 6 = f.hidden_field :noteable_type
7 7
8 - .note_text_and_preview.js-toggler-container  
9 - %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} }  
10 - %i.icon-eye-open  
11 - Preview  
12 - %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" }  
13 - %i.icon-edit  
14 - Write  
15 -  
16 - = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' 8 + .note_text_and_preview.js-toggler-container.notes-container
  9 + = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on markdown-area'
17 .note_preview.js-note-preview.turn-off 10 .note_preview.js-note-preview.turn-off
18 11
19 .hint 12 .hint
20 - .pull-right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 13 + .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  14 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
21 .clearfix 15 .clearfix
  16 + .error-alert
22 17
23 .note-form-actions 18 .note-form-actions
24 .buttons 19 .buttons
@@ -35,4 +30,14 @@ @@ -35,4 +30,14 @@
35 %span.file_name.js-attachment-filename File name... 30 %span.file_name.js-attachment-filename File name...
36 = f.file_field :attachment, class: "js-note-attachment-input hidden" 31 = f.file_field :attachment, class: "js-note-attachment-input hidden"
37 32
  33 + .write-preview-btn
  34 + %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} }
  35 + %i.icon-eye-open
  36 + Preview
  37 + %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" }
  38 + %i.icon-edit
  39 + Write
38 .clearfix 40 .clearfix
  41 +
  42 +:javascript
  43 + window.project_image_path_upload = "#{upload_image_project_path @project}";
app/views/projects/wikis/_form.html.haml
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 .col-sm-2 15 .col-sm-2
16 .col-sm-10 16 .col-sm-10
17 %p.cgray 17 %p.cgray
18 - Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.  
19 To link to a (new) page you can just type 18 To link to a (new) page you can just type
20 %code [Link Title](page-slug) 19 %code [Link Title](page-slug)
21 \. 20 \.
@@ -23,8 +22,12 @@ @@ -23,8 +22,12 @@
23 .form-group 22 .form-group
24 = f.label :content, class: 'control-label' 23 = f.label :content, class: 'control-label'
25 .col-sm-10 24 .col-sm-10
26 - = f.text_area :content, class: 'form-control js-gfm-input', rows: 18  
27 - 25 + = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18
  26 + .col-sm-12.hint
  27 + .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
  28 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
  29 + .clearfix
  30 + .error-alert
28 .form-group 31 .form-group
29 = f.label :commit_message, class: 'control-label' 32 = f.label :commit_message, class: 'control-label'
30 .col-sm-10= f.text_field :message, class: 'form-control', rows: 18 33 .col-sm-10= f.text_field :message, class: 'form-control', rows: 18
@@ -36,3 +39,7 @@ @@ -36,3 +39,7 @@
36 - else 39 - else
37 = f.submit 'Create page', class: "btn-create btn" 40 = f.submit 'Create page', class: "btn-create btn"
38 = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" 41 = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
  42 +
  43 +:javascript
  44 + window.project_image_path_upload = "#{upload_image_project_path @project}";
  45 +
config/routes.rb
@@ -136,8 +136,6 @@ Gitlab::Application.routes.draw do @@ -136,8 +136,6 @@ Gitlab::Application.routes.draw do
136 136
137 match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get 137 match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
138 138
139 -  
140 -  
141 # 139 #
142 # Dashboard Area 140 # Dashboard Area
143 # 141 #
@@ -178,6 +176,7 @@ Gitlab::Application.routes.draw do @@ -178,6 +176,7 @@ Gitlab::Application.routes.draw do
178 post :fork 176 post :fork
179 post :archive 177 post :archive
180 post :unarchive 178 post :unarchive
  179 + post :upload_image
181 get :autocomplete_sources 180 get :autocomplete_sources
182 get :import 181 get :import
183 put :retry_import 182 put :retry_import
spec/controllers/commits_controller_spec.rb
@@ -6,8 +6,7 @@ describe Projects::CommitsController do @@ -6,8 +6,7 @@ describe Projects::CommitsController do
6 6
7 before do 7 before do
8 sign_in(user) 8 sign_in(user)
9 -  
10 - project.team << [user, :master] 9 + project.creator = user
11 end 10 end
12 11
13 describe "GET show" do 12 describe "GET show" do
spec/controllers/projects_controller_spec.rb 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +require('spec_helper')
  2 +
  3 +describe ProjectsController do
  4 + let(:project) { create(:project) }
  5 + let(:user) { create(:user) }
  6 + let(:png) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') }
  7 + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
  8 + let(:gif) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
  9 + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
  10 +
  11 + describe "POST #upload_image" do
  12 + before do
  13 + sign_in(user)
  14 + end
  15 +
  16 + context "without params['markdown_img']" do
  17 + it "returns an error" do
  18 + post :upload_image, id: project.to_param
  19 + expect(response.status).to eq(404)
  20 + end
  21 + end
  22 +
  23 + context "with invalid file" do
  24 + before do
  25 + post :upload_image, id: project.to_param, markdown_img: @img
  26 + end
  27 +
  28 + it "returns an error" do
  29 + expect(response.status).to eq(404)
  30 + end
  31 + end
  32 +
  33 + context "with valid file" do
  34 + before do
  35 + post :upload_image, id: project.to_param, markdown_img: @img
  36 + end
  37 +
  38 + it "returns a content with original filename and new link." do
  39 + link = { alt: 'rails_sample', link: '' }.to_json
  40 + expect(response.body).to have_content link
  41 + end
  42 + end
  43 + end
  44 +end
0 \ No newline at end of file 45 \ No newline at end of file
spec/factories.rb
@@ -201,7 +201,7 @@ FactoryGirl.define do @@ -201,7 +201,7 @@ FactoryGirl.define do
201 end 201 end
202 202
203 trait :with_attachment do 203 trait :with_attachment do
204 - attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") } 204 + attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
205 end 205 end
206 end 206 end
207 207
spec/fixtures/banana_sample.gif 0 → 100644

70.1 KB

spec/fixtures/doc_sample.txt 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  2 +
  3 +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
0 \ No newline at end of file 4 \ No newline at end of file
spec/fixtures/rails_sample.jpg 0 → 100644

34.4 KB