diff --git a/CHANGELOG b/CHANGELOG index 2f7874ba724f9..ca0b96b840223 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v 7.0.0 + - The CPU no longer overheats when you hold down the spacebar + - Improve edit file UI + - Add ability to upload group avatar when create + v 6.9.0 - Store Rails cache data in the Redis `cache:gitlab` namespace - Adjust MySQL limits for existing installations @@ -18,6 +23,7 @@ v 6.9.0 - Accept merge request via API (sponsored by O'Reilly Media) - Add more access checks during API calls - Block SSH access for 'disabled' Active Directory users + - Labels for merge requests (Drew Blessing) v 6.8.0 - Ability to at mention users that are participating in issue and merge req. discussion diff --git a/Gemfile b/Gemfile index 7ff7515143c7c..9775334a7c1ea 100644 --- a/Gemfile +++ b/Gemfile @@ -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 @@ -163,6 +166,7 @@ gem 'select2-rails' gem 'jquery-atwho-rails', "~> 0.3.3" gem "jquery-rails" gem "jquery-ui-rails" +gem "jquery-scrollto-rails" gem "raphael-rails", "~> 2.1.2" gem 'bootstrap-sass', '~> 3.0' gem "font-awesome-rails", '~> 3.2' diff --git a/Gemfile.lock b/Gemfile.lock index f5f31105e184e..3d4a673af1ca5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -250,6 +252,8 @@ GEM jquery-rails (3.1.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) + jquery-scrollto-rails (1.4.3) + railties (> 3.1, < 5.0) jquery-turbolinks (2.0.1) railties (>= 3.1.0) turbolinks @@ -577,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 @@ -607,6 +612,7 @@ DEPENDENCIES jasmine (= 2.0.0.rc5) jquery-atwho-rails (~> 0.3.3) jquery-rails + jquery-scrollto-rails jquery-turbolinks jquery-ui-rails kaminari (~> 0.15.1) diff --git a/VERSION b/VERSION index fed1b9285ff85..2e45d00c58279 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.9.0.rc1 +7.0.0.pre diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index a22ff6dec3197..35e435546611b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -13,7 +13,7 @@ #= require jquery.history #= require jquery.waitforimages #= require jquery.atwho -#= require jquery.scrollto +#= require jquery.scrollTo #= require jquery.blockUI #= require turbolinks #= require jquery.turbolinks @@ -29,6 +29,7 @@ #= require underscore #= require nprogress #= require nprogress-turbolinks +#= require dropzone #= require_tree . window.slugify = (text) -> diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee index d06cb116dfed8..8ac5bfe95d874 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.coffee +++ b/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. diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee index 584f6faea161c..9db919e5a62c9 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob.js.coffee @@ -26,7 +26,7 @@ class BlobView unless isNaN first_line $("#tree-content-holder .highlight .line").removeClass("hll") $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $("#L#{first_line}").ScrollTo() unless e? + $.scrollTo("#L#{first_line}") unless e? # parse selected lines from hash # always return first and last line (initialized to NaN) diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee new file mode 100644 index 0000000000000..def5d12a820f9 --- /dev/null +++ b/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 = "
" + divSpinner = "
" + divAlert = "
" + iconPicture = "" + iconSpinner = "" + btnAlert = "" + project_image_path_upload = window.project_image_path_upload or null + + $("textarea.markdown-area").wrap "
" + + $(".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 \ No newline at end of file diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee index b0e39610feb31..a7fec96040681 100644 --- a/app/assets/javascripts/project_users_select.js.coffee +++ b/app/assets/javascripts/project_users_select.js.coffee @@ -37,7 +37,7 @@ projectUserFormatResult: (user) -> if user.avatar_url - avatar = user.avatar_url + avatar = gon.relative_url_root + user.avatar_url else if gon.gravatar_enabled avatar = gon.gravatar_url avatar = avatar.replace('%{hash}', md5(user.email)) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index ce9a505b1e3a6..cab9f6271eba8 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,7 +1,7 @@ $ -> userFormatResult = (user) -> if user.avatar_url - avatar = user.avatar_url + avatar = gon.relative_url_root + user.avatar_url else if gon.gravatar_enabled avatar = gon.gravatar_url avatar = avatar.replace('%{hash}', md5(user.email)) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c53873f95a2af..0b372a87a111c 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -10,6 +10,7 @@ *= require_self *= require nprogress *= require nprogress-bootstrap + *= require dropzone/basic */ @import "main/*"; diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 3fdc20485f278..64e3c8d9ace2b 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -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; } +} diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss new file mode 100644 index 0000000000000..a1fe18b02fa38 --- /dev/null +++ b/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; +} diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/sections/diff.scss index af44654d5da96..24d997c9fcafc 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/sections/diff.scss @@ -330,3 +330,8 @@ } } } + +.file-content .diff-file { + margin: 0; + border: none; +} diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index 7e56781f56a87..755295be1f403 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -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; @@ -310,7 +305,6 @@ ul.notes { float: none; } - .common-note-form { margin: 0; background: #F9F9F9; @@ -318,7 +312,6 @@ ul.notes { border: 1px solid #DDD; } - .note-form-actions { background: #F9F9F9; height: 45px; @@ -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 { @@ -367,3 +372,8 @@ ul.notes { .parallel-comment { padding: 6px; } + +.error-alert > .alert { + margin-top: 5px; + margin-bottom: 5px; +} diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index bf30de565ed09..7937454810d99 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -14,4 +14,3 @@ def download end end end - diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4e7a716bfe411..6eec2094f86bb 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -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 diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ebb8a90c63003..c15205fb68fb6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -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 @@ -190,6 +210,6 @@ def participants_in(type, id) end def sorted(users) - users.uniq.sort_by(&:username).map { |user| { username: user.username, name: user.name } } + users.uniq.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 16d51345e5a16..f0c2e5522738b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb new file mode 100644 index 0000000000000..cbc9271ac14c8 --- /dev/null +++ b/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 diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index bd4e3156af09b..41f8cb9da40bd 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -8,7 +8,7 @@ New project %ul.well-list - if projects.blank? - .nothing-here-block This groups has no projects yet + .nothing-here-block This group has no projects yet - projects.each do |project| %li.project-row = link_to project_path(project), class: dom_class(project) do diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 500c37ab71d41..2a5614cff6c39 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -45,7 +45,7 @@ - if @group.avatar? You can change your group avatar here - else - You can upload an group avatar here + You can upload a group avatar here %a.choose-btn.btn.btn-small.js-choose-group-avatar-button %i.icon-paper-clip %span Choose File ... diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 955a107e54270..ebf5e8571aa0b 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -13,6 +13,17 @@ .col-sm-10 = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + .form-group.group-description-holder + = f.label :avatar, "Group avatar", class: 'control-label' + .col-sm-10 + %a.choose-btn.btn.btn-small.js-choose-group-avatar-button + %i.icon-paper-clip + %span Choose File ... +   + %span.file_name.js-avatar-filename File name... + = f.file_field :avatar, class: "js-group-avatar-input hidden" + .light The maximum file size allowed is 100KB. + .form-group .col-sm-2 .col-sm-10 diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 0c62772a5c946..17475288a874c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -5,7 +5,10 @@ = link_to dashboard_path, class: 'btn btn-tiny' do ← To dashboard   - %span.cgray You will only see events from projects in this group + %span.cgray + Currently you are only seeing events from the + = @group.name + group %hr = render 'shared/event_filter' - if @events.any? diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 53e0dbaef9ba4..c7a827555a7a4 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -10,3 +10,4 @@ .container .content= yield + = yield :embedded_scripts \ No newline at end of file diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index 3ae4961b13752..11c815c52a764 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -14,3 +14,4 @@ .container .content= yield + = yield :embedded_scripts \ No newline at end of file diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/edit_tree/preview.html.haml index fc6d3bfbc2413..340f68cc05c4f 100644 --- a/app/views/projects/edit_tree/preview.html.haml +++ b/app/views/projects/edit_tree/preview.html.haml @@ -23,4 +23,4 @@ %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) - else - %p.nothing_here_message No changes. + .nothing-here-block No changes. diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index 93037ef958560..32a6d4ef36ed9 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -1,23 +1,26 @@ -%h3.page-title Edit mode .file-editor + %ul.nav.nav-tabs.js-edit-mode + %li.active + = link_to 'Edit', '#editor' + %li + = link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id) + = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do .file-holder.file .file-title - .btn-group.js-edit-mode.left-options - = link_to 'Edit', '#editor', class: 'active hover btn btn-tiny' - = link_to editing_preview_title(@blob.name), '#preview', class: 'btn btn-tiny', 'data-preview-url' => preview_project_edit_tree_path(@project, @id) %i.icon-file %span.file_name + %span.monospace.light #{@ref}: = @path - %small - on - %strong= @ref %span.options .btn-group.tree-btn-group = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } .file-content.code %pre.js-edit-mode-pane#editor= @blob.data .js-edit-mode-pane#preview.hide + %center + %h2 + %i.icon-spinner.icon-spin .form-group.commit_message-group = label_tag 'commit_message', class: "control-label" do @@ -61,14 +64,14 @@ paneId = currentLink.attr('href'), currentPane = editModePanes.filter(paneId); - editModeLinks.removeClass('active hover'); - currentLink.addClass('active hover'); + editModeLinks.parent().removeClass('active hover'); + currentLink.parent().addClass('active hover'); editModePanes.hide(); if (paneId == '#preview') { + currentPane.fadeIn(200); $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) { currentPane.empty().append(response); - currentPane.fadeIn(200); }) } else { currentPane.fadeIn(200); diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 84703229fe6be..49d1a87743f13 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -5,6 +5,7 @@ - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) .alert.alert-info.col-sm-10.col-sm-offset-2 ="Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe + = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f| -if @issue.errors.any? .alert.alert-danger @@ -19,8 +20,12 @@ .form-group = f.label :description, 'Description', class: 'control-label' .col-sm-10 - = f.text_area :description, class: "form-control js-gfm-input", rows: 14 - %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14 + .col-sm-12.hint + .pull-left Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix + .error-alert %hr .form-group .issue-assignee @@ -57,9 +62,6 @@ - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue) = link_to "Cancel", cancel_path, class: 'btn btn-cancel' - - - :javascript $("#issue_label_list") .bind( "keydown", function( event ) { @@ -94,3 +96,5 @@ $('#issue_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); + + window.project_image_path_upload = "#{upload_image_project_path @project}"; diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index b6d3a8edf4da9..2c816e788decc 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -73,4 +73,4 @@ = label.name   -.voting_notes#notes= render "projects/notes/notes_with_form" +.voting_notes#notes= render "projects/notes/notes_with_form" \ No newline at end of file diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 290a15e2664d5..3088f0cf864a3 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -22,8 +22,12 @@ .form-group = f.label :description, "Description", class: 'control-label' .col-sm-10 - = f.text_area :description, class: "form-control js-gfm-input", rows: 14 - %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14 + .col-sm-12.hint + .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix + .error-alert %hr .form-group .issue-assignee @@ -98,3 +102,5 @@ return false; } }); + + window.project_image_path_upload = "#{upload_image_project_path @project}"; diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index b5479be708b4d..dc41ef8135e08 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -23,8 +23,12 @@ .form-group .light = f.label :description, "Description" - = f.text_area :description, class: "form-control js-gfm-input", rows: 10 - %p.hint Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10 + .col-sm-12.hint + .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix + .error-alert .form-group .issue-assignee = f.label :assignee_id do @@ -80,3 +84,5 @@ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); + + window.project_image_path_upload = "#{upload_image_project_path @project}"; diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index d770bb5b3710b..7e0fd19d0abfd 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -21,8 +21,12 @@ .form-group = f.label :description, "Description", class: "control-label" .col-sm-10 - = f.text_area :description, maxlength: 2000, class: "form-control", rows: 10 - %p.hint Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + = f.text_area :description, maxlength: 2000, class: "form-control markdown-area", rows: 10 + .hint + .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix + .error-alert .col-md-6 .form-group = f.label :due_date, "Due Date", class: "control-label" @@ -45,3 +49,5 @@ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); + + window.project_image_path_upload = "#{upload_image_project_path @project}"; diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 3db551e993b07..cadae9da8050e 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -5,20 +5,15 @@ = f.hidden_field :noteable_id = f.hidden_field :noteable_type - .note_text_and_preview.js-toggler-container - %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} } - %i.icon-eye-open - Preview - %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" } - %i.icon-edit - Write - - = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on' + .note_text_and_preview.js-toggler-container.notes-container + = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on markdown-area' .note_preview.js-note-preview.turn-off .hint - .pull-right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix + .error-alert .note-form-actions .buttons @@ -35,4 +30,14 @@ %span.file_name.js-attachment-filename File name... = f.file_field :attachment, class: "js-note-attachment-input hidden" + .write-preview-btn + %a.btn.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {url: preview_project_notes_path(@project)} } + %i.icon-eye-open + Preview + %a.btn.btn-primary.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;" } + %i.icon-edit + Write .clearfix + +:javascript + window.project_image_path_upload = "#{upload_image_project_path @project}"; diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 0c2e33f22820c..a43e6f073e155 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -15,7 +15,6 @@ .col-sm-2 .col-sm-10 %p.cgray - Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. To link to a (new) page you can just type %code [Link Title](page-slug) \. @@ -23,8 +22,12 @@ .form-group = f.label :content, class: 'control-label' .col-sm-10 - = f.text_area :content, class: 'form-control js-gfm-input', rows: 18 - + = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18 + .col-sm-12.hint + .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix + .error-alert .form-group = f.label :commit_message, class: 'control-label' .col-sm-10= f.text_field :message, class: 'form-control', rows: 18 @@ -36,3 +39,7 @@ - else = f.submit 'Create page', class: "btn-create btn" = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" + +:javascript + window.project_image_path_upload = "#{upload_image_project_path @project}"; + diff --git a/config/routes.rb b/config/routes.rb index 7641fe430889b..da5a1ba7a871c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -136,8 +136,6 @@ match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get - - # # Dashboard Area # @@ -178,6 +176,7 @@ post :fork post :archive post :unarchive + post :upload_image get :autocomplete_sources get :import put :retry_import diff --git a/doc/update/6.8-to-6.9.md b/doc/update/6.8-to-6.9.md index a5e644b8a0789..4a3e45ae5eeb6 100644 --- a/doc/update/6.8-to-6.9.md +++ b/doc/update/6.8-to-6.9.md @@ -52,6 +52,12 @@ sudo -u git -H bundle install --without development test postgres --deployment # PostgreSQL installations (note: the line below states '--without ... mysql') sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production ``` ### 5. Update config files diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb index 820d0ef2a1fa4..5e0c71581f1cc 100644 --- a/features/steps/group/group.rb +++ b/features/steps/group/group.rb @@ -89,7 +89,7 @@ class Groups < Spinach::FeatureSteps Then 'I should see newly created group "Samurai"' do page.should have_content "Samurai" page.should have_content "Tokugawa Shogunate" - page.should have_content "You will only see events from projects in this group" + page.should have_content "Currently you are only seeing events from the" end And 'I change group "Owned" name to "new-name"' do diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index bf015a1fe1661..0387795fa4846 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -779,7 +779,7 @@ namespace :gitlab do end def check_gitlab_shell - required_version = Gitlab::VersionInfo.new(1, 9, 3) + required_version = Gitlab::VersionInfo.new(1, 9, 4) current_version = Gitlab::VersionInfo.parse(gitlab_shell_version) print "GitLab Shell version >= #{required_version} ? ... " diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb index fbf4f29acfd69..308cfa692192e 100644 --- a/spec/controllers/commits_controller_spec.rb +++ b/spec/controllers/commits_controller_spec.rb @@ -6,8 +6,7 @@ before do sign_in(user) - - project.team << [user, :master] + project.creator = user end describe "GET show" do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb new file mode 100644 index 0000000000000..07ca8d2502665 --- /dev/null +++ b/spec/controllers/projects_controller_spec.rb @@ -0,0 +1,44 @@ +require('spec_helper') + +describe ProjectsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:png) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:gif) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + + describe "POST #upload_image" do + before do + sign_in(user) + end + + context "without params['markdown_img']" do + it "returns an error" do + post :upload_image, id: project.to_param + expect(response.status).to eq(404) + end + end + + context "with invalid file" do + before do + post :upload_image, id: project.to_param, markdown_img: @img + end + + it "returns an error" do + expect(response.status).to eq(404) + end + end + + context "with valid file" do + before do + post :upload_image, id: project.to_param, markdown_img: @img + end + + it "returns a content with original filename and new link." do + link = { alt: 'rails_sample', link: '' }.to_json + expect(response.body).to have_content link + end + end + end +end \ No newline at end of file diff --git a/spec/factories.rb b/spec/factories.rb index 148477d638901..41cc99cbcb91e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -201,7 +201,7 @@ end trait :with_attachment do - attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png") } + attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index cfb6deb1834eb..45aebf128c299 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -132,7 +132,7 @@ end end -describe "On a merge request diff", js: true do +describe "On a merge request diff", js: true, feature: true do let(:merge_request) { create(:merge_request, :with_diffs, :simple) } let(:project) { merge_request.source_project } @@ -210,9 +210,3 @@ end end end - -describe "On merge request discussion", js: true do - describe "with merge request diff note" - describe "with commit note" - describe "with commit diff note" -end diff --git a/spec/fixtures/banana_sample.gif b/spec/fixtures/banana_sample.gif new file mode 100644 index 0000000000000..1322ac92d141f Binary files /dev/null and b/spec/fixtures/banana_sample.gif differ diff --git a/spec/fixtures/doc_sample.txt b/spec/fixtures/doc_sample.txt new file mode 100644 index 0000000000000..45dbc1aadde03 --- /dev/null +++ b/spec/fixtures/doc_sample.txt @@ -0,0 +1,3 @@ +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. + +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? \ No newline at end of file diff --git a/spec/fixtures/rails_sample.jpg b/spec/fixtures/rails_sample.jpg new file mode 100644 index 0000000000000..a847b19332535 Binary files /dev/null and b/spec/fixtures/rails_sample.jpg differ diff --git a/vendor/assets/javascripts/jquery.scrollto.js b/vendor/assets/javascripts/jquery.scrollto.js deleted file mode 100644 index 7f10b7f108254..0000000000000 --- a/vendor/assets/javascripts/jquery.scrollto.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * @depends jquery - * @name jquery.scrollto - * @package jquery-scrollto {@link http://balupton.com/projects/jquery-scrollto} - */ - -/** - * jQuery Aliaser - */ -(function(window,undefined){ - // Prepare - var jQuery, $, ScrollTo; - jQuery = $ = window.jQuery; - - /** - * jQuery ScrollTo (balupton edition) - * @version 1.2.0 - * @date July 9, 2012 - * @since 0.1.0, August 27, 2010 - * @package jquery-scrollto {@link http://balupton.com/projects/jquery-scrollto} - * @author Benjamin "balupton" Lupton {@link http://balupton.com} - * @copyright (c) 2010 Benjamin Arthur Lupton {@link http://balupton.com} - * @license MIT License {@link http://creativecommons.org/licenses/MIT/} - */ - ScrollTo = $.ScrollTo = $.ScrollTo || { - /** - * The Default Configuration - */ - config: { - duration: 400, - easing: 'swing', - callback: undefined, - durationMode: 'each', - offsetTop: 0, - offsetLeft: 0 - }, - - /** - * Configure ScrollTo - */ - configure: function(options){ - // Apply Options to Config - $.extend(ScrollTo.config, options||{}); - - // Chain - return this; - }, - - /** - * Perform the Scroll Animation for the Collections - * We use $inline here, so we can determine the actual offset start for each overflow:scroll item - * Each collection is for each overflow:scroll item - */ - scroll: function(collections, config){ - // Prepare - var collection, $container, container, $target, $inline, position, - containerScrollTop, containerScrollLeft, - containerScrollTopEnd, containerScrollLeftEnd, - startOffsetTop, targetOffsetTop, targetOffsetTopAdjusted, - startOffsetLeft, targetOffsetLeft, targetOffsetLeftAdjusted, - scrollOptions, - callback; - - // Determine the Scroll - collection = collections.pop(); - $container = collection.$container; - container = $container.get(0); - $target = collection.$target; - - // Prepare the Inline Element of the Container - $inline = $('').css({ - 'position': 'absolute', - 'top': '0px', - 'left': '0px' - }); - position = $container.css('position'); - - // Insert the Inline Element of the Container - $container.css('position','relative'); - $inline.appendTo($container); - - // Determine the top offset - startOffsetTop = $inline.offset().top; - targetOffsetTop = $target.offset().top; - targetOffsetTopAdjusted = targetOffsetTop - startOffsetTop - parseInt(config.offsetTop,10); - - // Determine the left offset - startOffsetLeft = $inline.offset().left; - targetOffsetLeft = $target.offset().left; - targetOffsetLeftAdjusted = targetOffsetLeft - startOffsetLeft - parseInt(config.offsetLeft,10); - - // Determine current scroll positions - containerScrollTop = container.scrollTop; - containerScrollLeft = container.scrollLeft; - - // Reset the Inline Element of the Container - $inline.remove(); - $container.css('position',position); - - // Prepare the scroll options - scrollOptions = {}; - - // Prepare the callback - callback = function(event){ - // Check - if ( collections.length === 0 ) { - // Callback - if ( typeof config.callback === 'function' ) { - config.callback.apply(this,[event]); - } - } - else { - // Recurse - ScrollTo.scroll(collections,config); - } - // Return true - return true; - }; - - // Handle if we only want to scroll if we are outside the viewport - if ( config.onlyIfOutside ) { - // Determine current scroll positions - containerScrollTopEnd = containerScrollTop + $container.height(); - containerScrollLeftEnd = containerScrollLeft + $container.width(); - - // Check if we are in the range of the visible area of the container - if ( containerScrollTop < targetOffsetTopAdjusted && targetOffsetTopAdjusted < containerScrollTopEnd ) { - targetOffsetTopAdjusted = containerScrollTop; - } - if ( containerScrollLeft < targetOffsetLeftAdjusted && targetOffsetLeftAdjusted < containerScrollLeftEnd ) { - targetOffsetLeftAdjusted = containerScrollLeft; - } - } - - // Determine the scroll options - if ( targetOffsetTopAdjusted !== containerScrollTop ) { - scrollOptions.scrollTop = targetOffsetTopAdjusted; - } - if ( targetOffsetLeftAdjusted !== containerScrollLeft ) { - scrollOptions.scrollLeft = targetOffsetLeftAdjusted; - } - - // Perform the scroll - if ( $.browser.safari && container === document.body ) { - window.scrollTo(scrollOptions.scrollLeft, scrollOptions.scrollTop); - callback(); - } - else if ( scrollOptions.scrollTop || scrollOptions.scrollLeft ) { - $container.animate(scrollOptions, config.duration, config.easing, callback); - } - else { - callback(); - } - - // Return true - return true; - }, - - /** - * ScrollTo the Element using the Options - */ - fn: function(options){ - // Prepare - var collections, config, $container, container; - collections = []; - - // Prepare - var $target = $(this); - if ( $target.length === 0 ) { - // Chain - return this; - } - - // Handle Options - config = $.extend({},ScrollTo.config,options); - - // Fetch - $container = $target.parent(); - container = $container.get(0); - - // Cycle through the containers - while ( ($container.length === 1) && (container !== document.body) && (container !== document) ) { - // Check Container for scroll differences - var scrollTop, scrollLeft; - scrollTop = $container.css('overflow-y') !== 'visible' && container.scrollHeight !== container.clientHeight; - scrollLeft = $container.css('overflow-x') !== 'visible' && container.scrollWidth !== container.clientWidth; - if ( scrollTop || scrollLeft ) { - // Push the Collection - collections.push({ - '$container': $container, - '$target': $target - }); - // Update the Target - $target = $container; - } - // Update the Container - $container = $container.parent(); - container = $container.get(0); - } - - // Add the final collection - collections.push({ - '$container': $( - ($.browser.msie || $.browser.mozilla) ? 'html' : 'body' - ), - '$target': $target - }); - - // Adjust the Config - if ( config.durationMode === 'all' ) { - config.duration /= collections.length; - } - - // Handle - ScrollTo.scroll(collections,config); - - // Chain - return this; - } - }; - - // Apply our jQuery Prototype Function - $.fn.ScrollTo = $.ScrollTo.fn; - -})(window);