Commit 1912e9d0 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'rs-quick-submit' into 'master'

Add "Quick Submit" JS behavior

This takes the "Command+Enter" (or "Ctrl+Enter") quick-post behavior from the discussion field and expands it to fields throughout the application, most notably the issuable forms (e.g., New Issue, Edit Merge Request).

See merge request !1516
parents c670f073 dbf9ccbc
...@@ -37,6 +37,8 @@ v 8.1.0 (unreleased) ...@@ -37,6 +37,8 @@ v 8.1.0 (unreleased)
- Use commit status in merge request widget as preffered source of CI status - Use commit status in merge request widget as preffered source of CI status
- Integrate CI commit and build pages into project pages - Integrate CI commit and build pages into project pages
- Move CI services page to project settings area - Move CI services page to project settings area
- Add "Quick Submit" behavior to input fields throughout the application. Use
Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux.
v 8.0.4 v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
# Quick Submit behavior
#
# When an input field with the `js-quick-submit` class receives a "Meta+Enter"
# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is
# submitted.
#
#= require extensions/jquery
#
# ### Example Markup
#
# <form action="/foo">
# <input type="text" class="js-quick-submit" />
# <textarea class="js-quick-submit"></textarea>
# </form>
#
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
return if (e.originalEvent && e.originalEvent.repeat) || e.repeat
return unless e.keyCode == 13 # Enter
if navigator.userAgent.match(/Macintosh/)
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
else
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault()
$form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable()
$form.submit()
...@@ -34,6 +34,5 @@ $.fn.requiresInput = -> ...@@ -34,6 +34,5 @@ $.fn.requiresInput = ->
$form.on 'change input', fieldSelector, requireInput $form.on 'change input', fieldSelector, requireInput
# Triggered on standard document `ready` and on Turbolinks `page:load` events $ ->
$(document).on 'ready page:load', ->
$('form.js-requires-input').requiresInput() $('form.js-requires-input').requiresInput()
...@@ -63,12 +63,6 @@ class @Notes ...@@ -63,12 +63,6 @@ class @Notes
# fetch notes when tab becomes visible # fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange $(document).on "visibilitychange", @visibilityChange
# Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
$(document).on 'keydown', '.js-note-text', (e) ->
return if e.originalEvent.repeat
if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
$(@).closest('form').submit()
cleanBinding: -> cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
...@@ -82,7 +76,6 @@ class @Notes ...@@ -82,7 +76,6 @@ class @Notes
$(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button" $(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange" $(document).off "visibilitychange"
$(document).off "keydown", ".js-note-text"
$(document).off "keyup", ".js-note-text" $(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-target-close"
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
- if current_action?(:new) || current_action?(:create) - if current_action?(:new) || current_action?(:create)
\/ \/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name", = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name' required: true, class: 'form-control new-file-name js-quick-submit'
.pull-right .pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= label_tag 'branch', class: 'control-label' do = label_tag 'branch', class: 'control-label' do
Branch Branch
.col-sm-10 .col-sm-10
= text_field_tag 'new_branch', @ref, class: "form-control" = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit"
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :title, class: "form-control", required: true = f.text_field :title, class: "form-control js-quick-submit", required: true
.form-group .form-group
= f.label :color, "Background Color", class: 'control-label' = f.label :color, "Background Color", class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -16,13 +16,13 @@ ...@@ -16,13 +16,13 @@
.form-group .form-group
= f.label :title, "Title", class: "control-label" = f.label :title, "Title", class: "control-label"
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control", required: true = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required %p.hint Required
.form-group.milestone-description .form-group.milestone-description
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control' = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.hint .hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
.pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
= note_target_fields(note) = note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field js-quick-submit'
= render 'projects/notes/hints' = render 'projects/notes/hints'
.note-form-actions .note-form-actions
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type = f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-quick-submit'
= render 'projects/notes/hints' = render 'projects/notes/hints'
.error-alert .error-alert
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
= f.label :content, class: 'control-label' = f.label :content, class: 'control-label'
.col-sm-10 .col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :content, classes: 'description form-control' = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit'
.col-sm-12.hint .col-sm-12.hint
.pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
.pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.max-width-marker .max-width-marker
= text_area_tag 'commit_message', = text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text]), (params[:commit_message] || local_assigns[:text]),
class: 'form-control', placeholder: local_assigns[:placeholder], class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder],
required: true, rows: (local_assigns[:rows] || 3) required: true, rows: (local_assigns[:rows] || 3)
- if local_assigns[:hint] - if local_assigns[:hint]
%p.hint %p.hint
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%strong= 'Title *' %strong= 'Title *'
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off', = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input', required: true class: 'form-control pad js-gfm-input js-quick-submit', required: true
- if issuable.is_a?(MergeRequest) - if issuable.is_a?(MergeRequest)
%p.help-block %p.help-block
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, = render 'projects/zen', f: f, attr: :description,
classes: 'description form-control' classes: 'description form-control js-quick-submit'
.col-sm-12.hint .col-sm-12.hint
.pull-left .pull-left
Parsed with Parsed with
......
#= require behaviors/quick_submit
describe 'Quick Submit behavior', ->
fixture.preload('behaviors/quick_submit.html')
beforeEach ->
fixture.load('behaviors/quick_submit.html')
# Prevent a form submit from moving us off the testing page
$('form').submit (e) -> e.preventDefault()
@spies = {
submit: spyOnEvent('form', 'submit')
}
it 'does not respond to other keyCodes', ->
$('input').trigger(keydownEvent(keyCode: 32))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to Enter alone', ->
$('input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'does not respond to repeated events', ->
$('input').trigger(keydownEvent(repeat: true))
expect(@spies.submit).not.toHaveBeenTriggered()
it 'disables submit buttons', ->
$('textarea').trigger(keydownEvent())
expect($('input[type=submit]')).toBeDisabled()
expect($('button[type=submit]')).toBeDisabled()
# We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
# only run the tests that apply to the current platform
if navigator.userAgent.match(/Macintosh/)
it 'responds to Meta+Enter', ->
$('input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(ctrlKey: true))
$('input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
else
it 'responds to Ctrl+Enter', ->
$('input').trigger(keydownEvent())
expect(@spies.submit).toHaveBeenTriggered()
it 'excludes other modifier keys', ->
$('input').trigger(keydownEvent(altKey: true))
$('input').trigger(keydownEvent(metaKey: true))
$('input').trigger(keydownEvent(shiftKey: true))
expect(@spies.submit).not.toHaveBeenTriggered()
keydownEvent = (options) ->
if navigator.userAgent.match(/Macintosh/)
defaults = { keyCode: 13, metaKey: true }
else
defaults = { keyCode: 13, ctrlKey: true }
$.Event('keydown', $.extend({}, defaults, options))
%form{ action: '/foo' }
%input.js-quick-submit{ type: 'text' }
%textarea.js-quick-submit
%input{ type: 'submit'} Submit
%button.btn{ type: 'submit' } Submit
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
# require the specific files that are being used in the spec that tests them. # require the specific files that are being used in the spec that tests them.
#= require jquery #= require jquery
#= require jquery.turbolinks
#= require bootstrap #= require bootstrap
#= require underscore #= require underscore
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment