Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements drag and drop upload of image in every markdown-area #7011

Merged
merged 1 commit into from May 23, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -69,6 +69,9 @@ gem "haml-rails"
# Files attachments
gem "carrierwave"

# Drag and Drop UI
gem 'dropzonejs-rails'

# for aws storage
gem "fog", "~> 1.14", group: :aws
gem "unf", group: :aws
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Expand Up @@ -103,6 +103,8 @@ GEM
diffy (3.0.3)
docile (1.1.1)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
email_spec (1.5.0)
launchy (~> 2.1)
mail (~> 2.2)
Expand Down Expand Up @@ -579,6 +581,7 @@ DEPENDENCIES
devise (= 3.0.4)
devise-async (= 0.8.0)
diffy (~> 3.0.3)
dropzonejs-rails
email_spec
email_validator (~> 1.4.0)
enumerize
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js.coffee
Expand Up @@ -29,6 +29,7 @@
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone
#= require_tree .

window.slugify = (text) ->
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/behaviors/toggler_behavior.coffee
@@ -1,6 +1,6 @@
$ ->
$("body").on "click", ".js-toggler-target", ->
container = $(@).closest(".js-toggler-container")
container = $(".notes-container")
container.toggleClass("on")

# Toggle button. Show/hide content inside parent container.
Expand Down
85 changes: 85 additions & 0 deletions app/assets/javascripts/markdown_area.js.coffee
@@ -0,0 +1,85 @@
formatLink = (str) ->
"![" + str.alt + "](" + str.url + ")"

$(document).ready ->
alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
divHover = "<div class=\"div-dropzone-hover\"></div>"
divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
divAlert = "<div class=\"" + alertClass + "\"></div>"
iconPicture = "<i class=\"icon-picture div-dropzone-icon\"></i>"
iconSpinner = "<i class=\"icon-spinner icon-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_image_path_upload = window.project_image_path_upload or null

$("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"

$(".div-dropzone").parent().addClass "div-dropzone-wrapper"

$(".div-dropzone").append divHover
$(".div-dropzone-hover").append iconPicture
$(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner


dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload
dictDefaultMessage: ""
clickable: true
paramName: "markdown_img"
maxFilesize: 10
uploadMultiple: false
acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")

previewContainer: false

processing: ->
$(".div-dropzone-alert").alert "close"

dragover: ->
$(".div-dropzone > textarea").addClass "div-dropzone-focus"
$(".div-dropzone-hover").css "opacity", 0.7
return

dragleave: ->
$(".div-dropzone > textarea").removeClass "div-dropzone-focus"
$(".div-dropzone-hover").css "opacity", 0
return

drop: ->
$(".div-dropzone > textarea").removeClass "div-dropzone-focus"
$(".div-dropzone-hover").css "opacity", 0
$(".div-dropzone > textarea").focus()
return

success: (header, response) ->
child = $(dropzone[0]).children("textarea")
$(child).val $(child).val() + formatLink(response.link) + "\n"
return

error: (temp, errorMessage) ->
checkIfMsgExists = $(".error-alert").children().length
if checkIfMsgExists is 0
$(".error-alert").append divAlert
$(".div-dropzone-alert").append btnAlert + errorMessage
return

sending: ->
$(".div-dropzone-spinner").css "opacity", 0.7
return

complete: ->
$(".dz-preview").remove()
$(".markdown-area").trigger "input"
$(".div-dropzone-spinner").css "opacity", 0
return
)

$(".markdown-selector").click (e) ->
e.preventDefault()
$(".div-dropzone").click()
return

return
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Expand Up @@ -10,6 +10,7 @@
*= require_self
*= require nprogress
*= require nprogress-bootstrap
*= require dropzone/basic
*/

@import "main/*";
Expand Down
13 changes: 11 additions & 2 deletions app/assets/stylesheets/behaviors.scss
Expand Up @@ -5,10 +5,19 @@
.js-details-container.open .content { display: block; }
.js-details-container.open .content.hide { display: none; }


// Toggler
//--------
.js-toggler-container .turn-on { display: inherit; }
.write-preview-btn .turn-on { display: inherit; }
.write-preview-btn .turn-off { display: none; }

.js-toggler-container .turn-off { display: none; }
.js-toggler-container.on .turn-on { display: none; }
.js-toggler-container.on .turn-off { display: inherit; }

.js-toggler-container.on ~ .note-form-actions {
.write-preview-btn .turn-on { display: none; }
}

.js-toggler-container.on ~ .note-form-actions {
.write-preview-btn .turn-off { display: inherit; }
}
58 changes: 58 additions & 0 deletions app/assets/stylesheets/generic/markdown_area.scss
@@ -0,0 +1,58 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
padding: 0;
border: 0;
margin-bottom: 5px;

.div-dropzone-focus {
border-color: #66afe9 !important;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
outline: 0 !important;
}

.div-dropzone-hover {
position: absolute;
top: 50%;
left: 50%;
margin-top: -0.5em;
margin-left: -0.6em;
opacity: 0;
font-size: 50px;
transition: opacity 200ms ease-in-out;
}

.div-dropzone-spinner {
position: absolute;
top: 100%;
left: 100%;
margin-top: -1.1em;
margin-left: -1.1em;
opacity: 0;
font-size: 30px;
transition: opacity 200ms ease-in-out;
}

.div-dropzone-icon {
display: block;
text-align: center;
font-size: inherit;
}

.dz-preview {
display: none;
}
}

.hint {
float: left;
padding: 0;
margin: 0;
}
}

.div-dropzone-alert {
margin-top: 5px;
margin-bottom: 0;
transition: opacity 200ms ease-in-out;
}
32 changes: 21 additions & 11 deletions app/assets/stylesheets/sections/notes.scss
Expand Up @@ -272,21 +272,16 @@ ul.notes {
margin-bottom: 0;
}
.note_text_and_preview {
// makes the "absolute" position for links relative to this
position: relative;

// preview/edit buttons
> a {
position: absolute;
right: 5px;
bottom: -60px;
}
.note_preview {
background: #f5f5f5;
border: 1px solid #ddd;
@include border-radius(4px);
min-height: 80px;
padding: 4px 6px;

> p {
overflow-x: auto;
}
}
.note_text {
border: 1px solid #DDD;
Expand All @@ -310,15 +305,13 @@ ul.notes {
float: none;
}


.common-note-form {
margin: 0;
background: #F9F9F9;
padding: 3px;
border: 1px solid #DDD;
}


.note-form-actions {
background: #F9F9F9;
height: 45px;
Expand All @@ -333,6 +326,18 @@ ul.notes {
.js-notify-commit-author {
float: left;
}

.write-preview-btn {
// makes the "absolute" position for links relative to this
position: relative;

// preview/edit buttons
> a {
position: absolute;
right: 5px;
top: 8px;
}
}
}

.note-edit-form {
Expand Down Expand Up @@ -367,3 +372,8 @@ ul.notes {
.parallel-comment {
padding: 6px;
}

.error-alert > .alert {
margin-top: 5px;
margin-bottom: 5px;
}
1 change: 0 additions & 1 deletion app/controllers/files_controller.rb
Expand Up @@ -14,4 +14,3 @@ def download
end
end
end

4 changes: 3 additions & 1 deletion app/controllers/projects/issues_controller.rb
Expand Up @@ -69,7 +69,9 @@ def create
render :new
end
end
format.js
format.js do |format|
@link = @issue.attachment.url.to_js
end
end
end

Expand Down
20 changes: 20 additions & 0 deletions app/controllers/projects_controller.rb
Expand Up @@ -162,8 +162,28 @@ def unarchive
end
end

def upload_image
uploader = FileUploader.new('uploads', upload_path, accepted_images)
alt = params['markdown_img'].original_filename
uploader.store!(params['markdown_img'])
link = { 'alt' => File.basename(alt, '.*'),
'url' => File.join(root_url, uploader.url) }
respond_to do |format|
format.json { render json: { link: link } }
end
end

private

def upload_path
base_dir = FileUploader.generate_dir
File.join(repository.path_with_namespace, base_dir)
end

def accepted_images
%w(png jpg jpeg gif)
end

def set_title
@title = 'New Project'
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/issue.rb
Expand Up @@ -15,8 +15,12 @@
# milestone_id :integer
# state :string(255)
# iid :integer
# attachment :string(255)
#

require 'carrierwave/orm/activerecord'
require 'file_size_validator'

class Issue < ActiveRecord::Base
include Issuable
include InternalId
Expand Down
41 changes: 41 additions & 0 deletions app/uploaders/file_uploader.rb
@@ -0,0 +1,41 @@
# encoding: utf-8
class FileUploader < CarrierWave::Uploader::Base
storage :file

def initialize(base_dir, path = '', allowed_extensions = nil)
@base_dir = base_dir
@path = path
@allowed_extensions = allowed_extensions
end

def base_dir
@base_dir
end

def store_dir
File.join(@base_dir, @path)
end

def cache_dir
File.join(@base_dir, 'tmp', @path)
end

def extension_white_list
@allowed_extensions
end

def store!(file)
file.original_filename = self.class.generate_filename(file)
super
end

def self.generate_filename(file)
original_filename = File.basename(file.original_filename, '.*')
extension = File.extname(file.original_filename)
new_filename = Digest::MD5.hexdigest(original_filename) + extension
end

def self.generate_dir
SecureRandom.hex(5)
end
end
1 change: 1 addition & 0 deletions app/views/layouts/admin.html.haml
Expand Up @@ -10,3 +10,4 @@

.container
.content= yield
= yield :embedded_scripts