Commit 6a85cdf1627629ecaa762fa60a7abdbd092cc20a
Committed by
erbunao
1 parent
696b9903
Exists in
spb-stable
and in
2 other branches
Implements drag and drop upload in creating issues
Showing
30 changed files
with
363 additions
and
46 deletions
Show diff stats
Gemfile
@@ -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 |
Gemfile.lock
@@ -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. |
@@ -0,0 +1,85 @@ | @@ -0,0 +1,85 @@ | ||
1 | +formatLink = (str) -> | ||
2 | + "" | ||
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 + ">×</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
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 | +} |
@@ -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
app/controllers/projects/issues_controller.rb
@@ -69,7 +69,9 @@ class Projects::IssuesController < Projects::ApplicationController | @@ -69,7 +69,9 @@ class Projects::IssuesController < 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 < ApplicationController | @@ -162,8 +162,28 @@ class ProjectsController < 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 |
@@ -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
app/views/layouts/projects.html.haml
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 | | 74 | |
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 |
@@ -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 |
70.1 KB
@@ -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 |
34.4 KB