Commit 60b4c88e3a175fa8b1fdebede4a5c5f73df0eee5
Exists in
master
and in
4 other branches
Merge pull request #1664 from riyad/auto-complete-everywhere
Cleanup auto-completion and add it to all GFM inputs
Showing
12 changed files
with
89 additions
and
66 deletions
Show diff stats
| @@ -0,0 +1,57 @@ | @@ -0,0 +1,57 @@ | ||
| 1 | + | ||
| 2 | +### | ||
| 3 | + Creates the variables for setting up GFM auto-completion | ||
| 4 | +### | ||
| 5 | +# Emoji | ||
| 6 | +window.autocompleteEmojiData = []; | ||
| 7 | +window.autocompleteEmojiTemplate = "<li data-value='${insert}'>${name} <img alt='${name}' height='20' src='${image}' width='20' /></li>"; | ||
| 8 | + | ||
| 9 | +# Team Members | ||
| 10 | +window.autocompleteMembersUrl = ""; | ||
| 11 | +window.autocompleteMembersParams = | ||
| 12 | + private_token: "" | ||
| 13 | + page: 1 | ||
| 14 | +window.autocompleteMembersData = []; | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +### | ||
| 19 | + Add GFM auto-completion to all input fields, that accept GFM input. | ||
| 20 | +### | ||
| 21 | +window.setupGfmAutoComplete = -> | ||
| 22 | + ### | ||
| 23 | + Emoji | ||
| 24 | + ### | ||
| 25 | + $('.gfm-input').atWho ':', | ||
| 26 | + data: autocompleteEmojiData, | ||
| 27 | + tpl: autocompleteEmojiTemplate | ||
| 28 | + | ||
| 29 | + ### | ||
| 30 | + Team Members | ||
| 31 | + ### | ||
| 32 | + $('.gfm-input').atWho '@', (query, callback) -> | ||
| 33 | + (getMoreMembers = -> | ||
| 34 | + $.getJSON(autocompleteMembersUrl, autocompleteMembersParams) | ||
| 35 | + .success (members) -> | ||
| 36 | + # pick the data we need | ||
| 37 | + newMembersData = $.map members, (m) -> m.name | ||
| 38 | + | ||
| 39 | + # add the new page of data to the rest | ||
| 40 | + $.merge autocompleteMembersData, newMembersData | ||
| 41 | + | ||
| 42 | + # show the pop-up with a copy of the current data | ||
| 43 | + callback autocompleteMembersData[..] | ||
| 44 | + | ||
| 45 | + # are we past the last page? | ||
| 46 | + if newMembersData.length == 0 | ||
| 47 | + # set static data and stop callbacks | ||
| 48 | + $('.gfm-input').atWho '@', | ||
| 49 | + data: autocompleteMembersData | ||
| 50 | + callback: null | ||
| 51 | + else | ||
| 52 | + # get next page | ||
| 53 | + getMoreMembers() | ||
| 54 | + | ||
| 55 | + # so the next request gets the next page | ||
| 56 | + autocompleteMembersParams.page += 1; | ||
| 57 | + ).call(); | ||
| 0 | \ No newline at end of file | 58 | \ No newline at end of file |
app/assets/javascripts/issues.js
| @@ -6,6 +6,7 @@ function switchToNewIssue(form){ | @@ -6,6 +6,7 @@ function switchToNewIssue(form){ | ||
| 6 | $("#new_issue_dialog").show("fade", { direction: "right" }, 150); | 6 | $("#new_issue_dialog").show("fade", { direction: "right" }, 150); |
| 7 | $('.top-tabs .add_new').hide(); | 7 | $('.top-tabs .add_new').hide(); |
| 8 | disableButtonIfEmptyField("#issue_title", ".save-btn"); | 8 | disableButtonIfEmptyField("#issue_title", ".save-btn"); |
| 9 | + setupGfmAutoComplete(); | ||
| 9 | }); | 10 | }); |
| 10 | } | 11 | } |
| 11 | 12 | ||
| @@ -17,6 +18,7 @@ function switchToEditIssue(form){ | @@ -17,6 +18,7 @@ function switchToEditIssue(form){ | ||
| 17 | $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); | 18 | $("#edit_issue_dialog").show("fade", { direction: "right" }, 150); |
| 18 | $('.add_new').hide(); | 19 | $('.add_new').hide(); |
| 19 | disableButtonIfEmptyField("#issue_title", ".save-btn"); | 20 | disableButtonIfEmptyField("#issue_title", ".save-btn"); |
| 21 | + setupGfmAutoComplete(); | ||
| 20 | }); | 22 | }); |
| 21 | } | 23 | } |
| 22 | 24 |
app/helpers/application_helper.rb
| @@ -98,6 +98,12 @@ module ApplicationHelper | @@ -98,6 +98,12 @@ module ApplicationHelper | ||
| 98 | [projects, default_nav, project_nav].flatten.to_json | 98 | [projects, default_nav, project_nav].flatten.to_json |
| 99 | end | 99 | end |
| 100 | 100 | ||
| 101 | + def emoji_autocomplete_source | ||
| 102 | + # should be an array of strings | ||
| 103 | + # so to_s can be called, because it is sufficient and to_json is too slow | ||
| 104 | + Emoji::NAMES.to_s | ||
| 105 | + end | ||
| 106 | + | ||
| 101 | def ldap_enable? | 107 | def ldap_enable? |
| 102 | Devise.omniauth_providers.include?(:ldap) | 108 | Devise.omniauth_providers.include?(:ldap) |
| 103 | end | 109 | end |
app/helpers/notes_helper.rb
| @@ -14,10 +14,4 @@ module NotesHelper | @@ -14,10 +14,4 @@ module NotesHelper | ||
| 14 | "vote downvote" | 14 | "vote downvote" |
| 15 | end | 15 | end |
| 16 | end | 16 | end |
| 17 | - | ||
| 18 | - def emoji_for_completion | ||
| 19 | - # should be an array of strings | ||
| 20 | - # so to_s can be called, because it is sufficient and to_json is too slow | ||
| 21 | - Emoji::NAMES | ||
| 22 | - end | ||
| 23 | end | 17 | end |
app/views/issues/_form.html.haml
| @@ -12,7 +12,7 @@ | @@ -12,7 +12,7 @@ | ||
| 12 | = f.label :title do | 12 | = f.label :title do |
| 13 | %strong= "Subject *" | 13 | %strong= "Subject *" |
| 14 | .input | 14 | .input |
| 15 | - = f.text_field :title, maxlength: 255, class: "xxlarge" | 15 | + = f.text_field :title, maxlength: 255, class: "xxlarge gfm-input" |
| 16 | .issue_middle_block | 16 | .issue_middle_block |
| 17 | .issue_assignee | 17 | .issue_assignee |
| 18 | = f.label :assignee_id do | 18 | = f.label :assignee_id do |
| @@ -37,7 +37,7 @@ | @@ -37,7 +37,7 @@ | ||
| 37 | .clearfix | 37 | .clearfix |
| 38 | = f.label :description, "Details" | 38 | = f.label :description, "Details" |
| 39 | .input | 39 | .input |
| 40 | - = f.text_area :description, maxlength: 2000, class: "xxlarge", rows: 14 | 40 | + = f.text_area :description, maxlength: 2000, class: "xxlarge gfm-input", rows: 14 |
| 41 | %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. | 41 | %p.hint Issues are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}. |
| 42 | 42 | ||
| 43 | 43 |
app/views/layouts/_head_panel.html.haml
| @@ -28,6 +28,8 @@ | @@ -28,6 +28,8 @@ | ||
| 28 | My profile | 28 | My profile |
| 29 | = link_to 'Logout', destroy_user_session_path, class: "logout", method: :delete | 29 | = link_to 'Logout', destroy_user_session_path, class: "logout", method: :delete |
| 30 | 30 | ||
| 31 | += render "layouts/init_auto_complete" | ||
| 32 | + | ||
| 31 | :javascript | 33 | :javascript |
| 32 | $(function(){ | 34 | $(function(){ |
| 33 | $("#search").autocomplete({ | 35 | $("#search").autocomplete({ |
| @@ -0,0 +1,17 @@ | @@ -0,0 +1,17 @@ | ||
| 1 | +:javascript | ||
| 2 | + $(function() { | ||
| 3 | + autocompleteMembersUrl = "#{ "/api/v2/projects/#{@project.code}/members" if @project }"; | ||
| 4 | + autocompleteMembersParams.private_token = "#{current_user.authentication_token}"; | ||
| 5 | + | ||
| 6 | + autocompleteEmojiData = #{raw emoji_autocomplete_source}; | ||
| 7 | + // convert the list so that the items have the right format for completion | ||
| 8 | + autocompleteEmojiData = $.map(autocompleteEmojiData, function(value) { | ||
| 9 | + return { | ||
| 10 | + name: value, | ||
| 11 | + insert: value+':', | ||
| 12 | + image: '#{image_path("emoji")}/'+value+'.png' | ||
| 13 | + } | ||
| 14 | + }); | ||
| 15 | + | ||
| 16 | + setupGfmAutoComplete(); | ||
| 17 | + }); |
app/views/merge_requests/_form.html.haml
| @@ -38,7 +38,7 @@ | @@ -38,7 +38,7 @@ | ||
| 38 | .top_box_content | 38 | .top_box_content |
| 39 | = f.label :title do | 39 | = f.label :title do |
| 40 | %strong= "Title *" | 40 | %strong= "Title *" |
| 41 | - .input= f.text_field :title, class: "input-xxlarge pad", maxlength: 255, rows: 5 | 41 | + .input= f.text_field :title, class: "input-xxlarge pad gfm-input", maxlength: 255, rows: 5 |
| 42 | .middle_box_content | 42 | .middle_box_content |
| 43 | = f.label :assignee_id do | 43 | = f.label :assignee_id do |
| 44 | %i.icon-user | 44 | %i.icon-user |
app/views/notes/_common_form.html.haml
| @@ -36,49 +36,3 @@ | @@ -36,49 +36,3 @@ | ||
| 36 | %a.file_upload.btn.small Upload File | 36 | %a.file_upload.btn.small Upload File |
| 37 | = f.file_field :attachment, class: "input-file" | 37 | = f.file_field :attachment, class: "input-file" |
| 38 | %span.hint Any file less than 10 MB | 38 | %span.hint Any file less than 10 MB |
| 39 | - | ||
| 40 | -:javascript | ||
| 41 | - $(function(){ | ||
| 42 | - // init auto-completion of team members | ||
| 43 | - var membersUrl = "#{root_url}/api/v2/projects/#{@project.code}/members"; | ||
| 44 | - var membersParams = { | ||
| 45 | - private_token: "#{current_user.authentication_token}", | ||
| 46 | - page: 1, | ||
| 47 | - }; | ||
| 48 | - var membersData = []; | ||
| 49 | - $('.gfm-input').atWho('@', function(query, callback) { | ||
| 50 | - (function getMoreMembers() { | ||
| 51 | - $.getJSON(membersUrl, membersParams). | ||
| 52 | - success(function(members) { | ||
| 53 | - // pick the data we need | ||
| 54 | - var newMembersData = $.map(members, function(member) { return member.name }); | ||
| 55 | - | ||
| 56 | - // add the new page of data to the rest | ||
| 57 | - $.merge(membersData, newMembersData); | ||
| 58 | - | ||
| 59 | - // show the pop-up with a copy of the current data | ||
| 60 | - callback(membersData.slice(0)); | ||
| 61 | - | ||
| 62 | - // are we past the last page? | ||
| 63 | - if (newMembersData.length == 0) { | ||
| 64 | - // set static data and stop callbacks | ||
| 65 | - $('.gfm-input').atWho('@', { data: membersData, callback: null }); | ||
| 66 | - } else { | ||
| 67 | - // get next page | ||
| 68 | - getMoreMembers(); | ||
| 69 | - } | ||
| 70 | - }); | ||
| 71 | - // next request will get the next page | ||
| 72 | - membersParams.page += 1; | ||
| 73 | - })(); | ||
| 74 | - }); | ||
| 75 | - | ||
| 76 | - // init auto-completion of emoji | ||
| 77 | - var emoji = #{emoji_for_completion}; | ||
| 78 | - // convert the list so that the items have the right format for completion | ||
| 79 | - emoji = $.map(emoji, function(value) {return { key: value+':', name: value }}); | ||
| 80 | - $('.gfm-input').atWho(':', { | ||
| 81 | - data: emoji, | ||
| 82 | - tpl: "<li data-value='${key}'>${name} #{escape_javascript image_tag('emoji/${name}.png', :size => '20x20')}</li>" | ||
| 83 | - }); | ||
| 84 | - }); |
app/views/notes/_create_common_note.js.haml
app/views/wikis/_form.html.haml
| @@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
| 21 | 21 | ||
| 22 | .bottom_box_content | 22 | .bottom_box_content |
| 23 | = f.label :content | 23 | = f.label :content |
| 24 | - .input= f.text_area :content, class: 'span8' | 24 | + .input= f.text_area :content, class: 'span8 gfm-input' |
| 25 | .actions | 25 | .actions |
| 26 | = f.submit 'Save', class: "save-btn btn" | 26 | = f.submit 'Save', class: "save-btn btn" |
| 27 | = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn" | 27 | = link_to "Cancel", project_wiki_path(@project, :index), class: "btn cancel-btn" |
spec/helpers/notes_helper_spec.rb
| @@ -1,10 +0,0 @@ | @@ -1,10 +0,0 @@ | ||
| 1 | -require 'spec_helper' | ||
| 2 | - | ||
| 3 | -describe NotesHelper do | ||
| 4 | - describe "#emoji_for_completion" do | ||
| 5 | - it "should be an Array of Strings" do | ||
| 6 | - emoji_for_completion.should be_a(Array) | ||
| 7 | - emoji_for_completion.each { |emoji| emoji.should be_a(String) } | ||
| 8 | - end | ||
| 9 | - end | ||
| 10 | -end |