Skip to content

Commit

Permalink
Merge pull request #7011 from erbunao/drag-and-drop_markdown
Browse files Browse the repository at this point in the history
Implements drag and drop upload of image in every markdown-area
  • Loading branch information
dzaporozhets committed May 23, 2014
2 parents 696b990 + 6a85cdf commit fc86165
Show file tree
Hide file tree
Showing 30 changed files with 363 additions and 46 deletions.
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

0 comments on commit fc86165

Please sign in to comment.