Commit 64b1956b0bd95abbed25409867cbe93347ea3b71

Authored by Dmitriy Zaporozhets
2 parents 260c877c 22732346

Merge pull request #7032 from erbunao/markdown_clipboard

Implements clipboard feature for markdown areas.
app/assets/javascripts/markdown_area.js.coffee
1 -formatLink = (str) ->  
2 - "![" + str.alt + "](" + str.url + ")"  
3 -  
4 $(document).ready -> 1 $(document).ready ->
5 alertClass = "alert alert-danger alert-dismissable div-dropzone-alert" 2 alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"
6 alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"" 3 alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""
@@ -13,15 +10,12 @@ $(document).ready -> @@ -13,15 +10,12 @@ $(document).ready ->
13 project_image_path_upload = window.project_image_path_upload or null 10 project_image_path_upload = window.project_image_path_upload or null
14 11
15 $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>" 12 $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
16 -  
17 $(".div-dropzone").parent().addClass "div-dropzone-wrapper" 13 $(".div-dropzone").parent().addClass "div-dropzone-wrapper"
18 -  
19 $(".div-dropzone").append divHover 14 $(".div-dropzone").append divHover
20 $(".div-dropzone-hover").append iconPicture 15 $(".div-dropzone-hover").append iconPicture
21 $(".div-dropzone").append divSpinner 16 $(".div-dropzone").append divSpinner
22 $(".div-dropzone-spinner").append iconSpinner 17 $(".div-dropzone-spinner").append iconSpinner
23 18
24 -  
25 dropzone = $(".div-dropzone").dropzone( 19 dropzone = $(".div-dropzone").dropzone(
26 url: project_image_path_upload 20 url: project_image_path_upload
27 dictDefaultMessage: "" 21 dictDefaultMessage: ""
@@ -36,7 +30,7 @@ $(document).ready -&gt; @@ -36,7 +30,7 @@ $(document).ready -&gt;
36 previewContainer: false 30 previewContainer: false
37 31
38 processing: -> 32 processing: ->
39 - $(".div-dropzone-alert").alert "close" 33 + closeAlertMessage()
40 34
41 dragover: -> 35 dragover: ->
42 $(".div-dropzone > textarea").addClass "div-dropzone-focus" 36 $(".div-dropzone > textarea").addClass "div-dropzone-focus"
@@ -55,31 +49,127 @@ $(document).ready -&gt; @@ -55,31 +49,127 @@ $(document).ready -&gt;
55 return 49 return
56 50
57 success: (header, response) -> 51 success: (header, response) ->
58 - child = $(dropzone[0]).children("textarea")  
59 - $(child).val $(child).val() + formatLink(response.link) + "\n" 52 + appendToTextArea(formatLink(response.link))
60 return 53 return
61 54
62 error: (temp, errorMessage) -> 55 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 56 + showError(errorMessage)
67 return 57 return
68 58
69 sending: -> 59 sending: ->
70 - $(".div-dropzone-spinner").css "opacity", 0.7 60 + showSpinner()
71 return 61 return
72 62
73 complete: -> 63 complete: ->
74 $(".dz-preview").remove() 64 $(".dz-preview").remove()
75 $(".markdown-area").trigger "input" 65 $(".markdown-area").trigger "input"
76 - $(".div-dropzone-spinner").css "opacity", 0 66 + closeSpinner()
77 return 67 return
78 ) 68 )
79 69
  70 + child = $(dropzone[0]).children("textarea")
  71 +
  72 + formatLink = (str) ->
  73 + "![" + str.alt + "](" + str.url + ")"
  74 +
  75 + handlePaste = (e) ->
  76 + e.preventDefault()
  77 + my_event = e.originalEvent
  78 +
  79 + if my_event.clipboardData and my_event.clipboardData.items
  80 + i = 0
  81 + while i < my_event.clipboardData.items.length
  82 + item = my_event.clipboardData.items[i]
  83 + processItem(my_event, item)
  84 + i++
  85 +
  86 + processItem = (e, item) ->
  87 + if isImage(item)
  88 + filename = getFilename(e) or "image.png"
  89 + text = "{{" + filename + "}}"
  90 + pasteText(text)
  91 + uploadFile item.getAsFile(), filename
  92 + else if e.clipboardData.items.length == 1
  93 + text = e.clipboardData.getData("text/plain")
  94 + pasteText(text)
  95 +
  96 + isImage = (item) ->
  97 + if item
  98 + item.type.indexOf("image") isnt -1
  99 +
  100 + pasteText = (text) ->
  101 + caretStart = $(child)[0].selectionStart
  102 + caretEnd = $(child)[0].selectionEnd
  103 + textEnd = $(child).val().length
  104 +
  105 + beforeSelection = $(child).val().substring 0, caretStart
  106 + afterSelection = $(child).val().substring caretEnd, textEnd
  107 + $(child).val beforeSelection + text + afterSelection
  108 + $(".markdown-area").trigger "input"
  109 +
  110 + getFilename = (e) ->
  111 + if window.clipboardData and window.clipboardData.getData
  112 + value = window.clipboardData.getData("Text")
  113 + else if e.clipboardData and e.clipboardData.getData
  114 + value = e.clipboardData.getData("text/plain")
  115 +
  116 + value = value.split("\r")
  117 + value.first()
  118 +
  119 + uploadFile = (item, filename) ->
  120 + formData = new FormData()
  121 + formData.append "markdown_img", item, filename
  122 + $.ajax
  123 + url: project_image_path_upload
  124 + type: "POST"
  125 + data: formData
  126 + dataType: "json"
  127 + processData: false
  128 + contentType: false
  129 + headers:
  130 + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
  131 +
  132 + beforeSend: ->
  133 + showSpinner()
  134 + closeAlertMessage()
  135 +
  136 + success: (e, textStatus, response) ->
  137 + insertToTextArea(filename, formatLink(response.responseJSON.link))
  138 +
  139 + error: (response) ->
  140 + showError(response.responseJSON.message)
  141 +
  142 + complete: ->
  143 + closeSpinner()
  144 +
  145 + insertToTextArea = (filename, url) ->
  146 + $(child).val (index, val) ->
  147 + val.replace("{{" + filename + "}}", url + "\n")
  148 +
  149 + appendToTextArea = (url) ->
  150 + $(child).val (index, val) ->
  151 + val + url + "\n"
  152 +
  153 + showSpinner = (e) ->
  154 + $(".div-dropzone-spinner").css "opacity", 0.7
  155 +
  156 + closeSpinner = ->
  157 + $(".div-dropzone-spinner").css "opacity", 0
  158 +
  159 + showError = (message) ->
  160 + checkIfMsgExists = $(".error-alert").children().length
  161 + if checkIfMsgExists is 0
  162 + $(".error-alert").append divAlert
  163 + $(".div-dropzone-alert").append btnAlert + message
  164 +
  165 + closeAlertMessage = ->
  166 + $(".div-dropzone-alert").alert "close"
  167 +
80 $(".markdown-selector").click (e) -> 168 $(".markdown-selector").click (e) ->
81 e.preventDefault() 169 e.preventDefault()
82 $(".div-dropzone").click() 170 $(".div-dropzone").click()
83 return 171 return
84 172
  173 + $(".div-dropzone").on "paste", handlePaste
  174 +
85 return 175 return
86 \ No newline at end of file 176 \ No newline at end of file
app/assets/stylesheets/generic/markdown_area.scss
@@ -43,6 +43,11 @@ @@ -43,6 +43,11 @@
43 display: none; 43 display: none;
44 } 44 }
45 } 45 }
  46 +
  47 + .hint {
  48 + padding: 0;
  49 + margin: 0;
  50 + }
46 } 51 }
47 52
48 .div-dropzone-alert { 53 .div-dropzone-alert {
app/controllers/projects_controller.rb
@@ -11,6 +11,8 @@ class ProjectsController &lt; ApplicationController @@ -11,6 +11,8 @@ class ProjectsController &lt; ApplicationController
11 layout 'navless', only: [:new, :create, :fork] 11 layout 'navless', only: [:new, :create, :fork]
12 before_filter :set_title, only: [:new, :create] 12 before_filter :set_title, only: [:new, :create]
13 13
  14 + rescue_from CarrierWave::IntegrityError, with: :invalid_file
  15 +
14 def new 16 def new
15 @project = Project.new 17 @project = Project.new
16 end 18 end
@@ -185,6 +187,10 @@ class ProjectsController &lt; ApplicationController @@ -185,6 +187,10 @@ class ProjectsController &lt; ApplicationController
185 %w(png jpg jpeg gif) 187 %w(png jpg jpeg gif)
186 end 188 end
187 189
  190 + def invalid_file(error)
  191 + render json: { message: error.message }, status: :internal_server_error
  192 + end
  193 +
188 def set_title 194 def set_title
189 @title = 'New Project' 195 @title = 'New Project'
190 end 196 end
app/views/projects/issues/_form.html.haml
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14 23 = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14
24 .col-sm-12.hint 24 .col-sm-12.hint
25 .pull-left Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 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' }. 26 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
27 .clearfix 27 .clearfix
28 .error-alert 28 .error-alert
29 %hr 29 %hr
app/views/projects/merge_requests/_form.html.haml
@@ -25,7 +25,8 @@ @@ -25,7 +25,8 @@
25 = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14 25 = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14
26 .col-sm-12.hint 26 .col-sm-12.hint
27 .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 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' }. 28 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
  29 +
29 .clearfix 30 .clearfix
30 .error-alert 31 .error-alert
31 %hr 32 %hr
app/views/projects/merge_requests/_new_submit.html.haml
@@ -26,7 +26,8 @@ @@ -26,7 +26,8 @@
26 = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10 26 = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10
27 .col-sm-12.hint 27 .col-sm-12.hint
28 .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 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' }. 29 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
  30 +
30 .clearfix 31 .clearfix
31 .error-alert 32 .error-alert
32 .form-group 33 .form-group
app/views/projects/milestones/_form.html.haml
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 = f.text_area :description, maxlength: 2000, class: "form-control markdown-area", rows: 10 24 = f.text_area :description, maxlength: 2000, class: "form-control markdown-area", rows: 10
25 .hint 25 .hint
26 .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 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' }. 27 + .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
28 .clearfix 28 .clearfix
29 .error-alert 29 .error-alert
30 .col-md-6 30 .col-md-6
app/views/projects/notes/_form.html.haml
@@ -16,10 +16,10 @@ @@ -16,10 +16,10 @@
16 .note-write-holder 16 .note-write-holder
17 = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area' 17 = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area'
18 18
19 - .light.clearfix 19 + .light.clearfix.hint
20 .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 20 .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
21 - .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.  
22 - 21 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
  22 + .error-alert
23 .note-preview-holder.hide 23 .note-preview-holder.hide
24 .js-note-preview 24 .js-note-preview
25 25
app/views/projects/wikis/_form.html.haml
@@ -25,7 +25,8 @@ @@ -25,7 +25,8 @@
25 = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18 25 = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18
26 .col-sm-12.hint 26 .col-sm-12.hint
27 .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. 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' }. 28 + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping, #{link_to "selecting them", '#', class: 'markdown-selector' } or pasting from the clipboard.
  29 +
29 .clearfix 30 .clearfix
30 .error-alert 31 .error-alert
31 .form-group 32 .form-group