diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb index 3b87d9e81..d62b75155 100644 --- a/app/controllers/auto_completes_controller.rb +++ b/app/controllers/auto_completes_controller.rb @@ -42,6 +42,19 @@ class AutoCompletesController < ApplicationController render :json => format_issues_json(issues) end + def wiki_pages + q = params[:q].to_s.strip + wiki = Wiki.find_by(project: @project) + scope = (@project && wiki ? wiki.pages : WikiPage.all).reorder(id: :desc) + wiki_pages = + if q.present? + scope.where("LOWER(#{WikiPage.table_name}.title) LIKE LOWER(?)", "%#{q}%").limit(10).to_a + else + scope.limit(10).to_a + end + render :json => format_wiki_pages_json(wiki_pages) + end + private def find_project @@ -61,4 +74,14 @@ class AutoCompletesController < ApplicationController } } end + + def format_wiki_pages_json(wiki_pages) + wiki_pages.map {|wiki_page| + { + 'id' => wiki_page.id, + 'label' => wiki_page.title.to_s.truncate(255), + 'value' => wiki_page.title + } + } + end end diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index 6cf09c8ab..b5f5300d9 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -85,7 +85,8 @@ module CustomFieldsHelper css += ' wiki-edit' data = { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => custom_value.customized.project, :q => '') } if custom_value.customized&.try(:project) end custom_value.custom_field.format.edit_tag( @@ -134,7 +135,8 @@ module CustomFieldsHelper css += ' wiki-edit' data = { :auto_complete => true, - :issues_url => auto_complete_issues_path(:q => '') + :issues_url => auto_complete_issues_path(:q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:q => '') } end custom_field.format.bulk_edit_tag( diff --git a/app/views/documents/_form.html.erb b/app/views/documents/_form.html.erb index b8b8c8404..804af2ff2 100644 --- a/app/views/documents/_form.html.erb +++ b/app/views/documents/_form.html.erb @@ -6,7 +6,8 @@

<%= f.text_area :description, :cols => 60, :rows => 15, :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %>

diff --git a/app/views/issues/_edit.html.erb b/app/views/issues/_edit.html.erb index cc2f110b7..fa42928f7 100644 --- a/app/views/issues/_edit.html.erb +++ b/app/views/issues/_edit.html.erb @@ -32,7 +32,8 @@ <%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @issue.project, :q => '') }, :no_label => true %> <%= wikitoolbar_for 'issue_notes', preview_issue_path(:project_id => @project, :issue_id => @issue) %> diff --git a/app/views/issues/_form.html.erb b/app/views/issues/_form.html.erb index 214502b0a..6426de427 100644 --- a/app/views/issues/_form.html.erb +++ b/app/views/issues/_form.html.erb @@ -38,7 +38,8 @@ :rows => [[10, @issue.description.to_s.length / 50].max, 20].min, :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @issue.project, :q => '') }, :no_label => true %> <% end %> diff --git a/app/views/issues/bulk_edit.html.erb b/app/views/issues/bulk_edit.html.erb index 224ace22a..20a4ffa6c 100644 --- a/app/views/issues/bulk_edit.html.erb +++ b/app/views/issues/bulk_edit.html.erb @@ -222,7 +222,8 @@ <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %> <%= wikitoolbar_for 'notes' %> diff --git a/app/views/journals/_notes_form.html.erb b/app/views/journals/_notes_form.html.erb index aeb4b6a93..bab864593 100644 --- a/app/views/journals/_notes_form.html.erb +++ b/app/views/journals/_notes_form.html.erb @@ -7,7 +7,8 @@ :rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min), :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %> <% if @journal.safe_attribute? 'private_notes' %> diff --git a/app/views/messages/_form.html.erb b/app/views/messages/_form.html.erb index 7d3a9c59f..adee28d38 100644 --- a/app/views/messages/_form.html.erb +++ b/app/views/messages/_form.html.erb @@ -27,7 +27,8 @@ :accesskey => accesskey(:edit), :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %>

<%= wikitoolbar_for 'message_content', preview_board_message_path(:board_id => @board, :id => @message) %> diff --git a/app/views/news/_form.html.erb b/app/views/news/_form.html.erb index 58f68e1b2..12379f1de 100644 --- a/app/views/news/_form.html.erb +++ b/app/views/news/_form.html.erb @@ -6,7 +6,8 @@

<%= f.text_area :description, :required => true, :cols => 60, :rows => 15, :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %>

<%= render :partial => 'attachments/form', :locals => {:container => @news} %>

diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 04ea54a0a..fa7d8f69b 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -56,7 +56,8 @@ <%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %> <%= wikitoolbar_for 'comment_comments', preview_news_path(:project_id => @project, :id => @news) %> diff --git a/app/views/wiki/edit.html.erb b/app/views/wiki/edit.html.erb index 0e1c23cc4..a631be927 100644 --- a/app/views/wiki/edit.html.erb +++ b/app/views/wiki/edit.html.erb @@ -17,7 +17,8 @@ :class => 'wiki-edit', :data => { :auto_complete => true, - :issues_url => auto_complete_issues_path(:project_id => @project, :q => '') + :issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), + :wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') } %> diff --git a/config/routes.rb b/config/routes.rb index 03071fad9..037317890 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,8 +43,10 @@ Rails.application.routes.draw do post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' - # Misc issue routes. TODO: move into resources + # Auto complate routes match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' + match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages' + match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' diff --git a/lib/redmine.rb b/lib/redmine.rb index 668a65da2..b83f7a287 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -154,7 +154,7 @@ Redmine::AccessControl.map do |map| end map.project_module :wiki do |map| - map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true + map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index], :auto_complete => [:wiki_pages]}, :read => true map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true map.permission :edit_wiki_pages, :wiki => [:new, :edit, :update, :preview, :add_attachment], :attachments => :upload diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 7076aabcb..27f570c3e 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1040,9 +1040,6 @@ function inlineAutoComplete(element) { // do not attach if Tribute is already initialized if (element.dataset.tribute === 'true') {return;} - const issuesUrl = element.dataset.issuesUrl; - const usersUrl = element.dataset.usersUrl; - const remoteSearch = function(url, cb) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () @@ -1060,31 +1057,55 @@ function inlineAutoComplete(element) { xhr.send(); }; - const tribute = new Tribute({ - trigger: '#', - values: function (text, cb) { - if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { - $(element).attr('autocomplete', 'off'); + const AutoCompleteIssueId = + { + trigger: '#', + values: function (text, cb) { + if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { + $(element).attr('autocomplete', 'off'); + } + remoteSearch(element.dataset.issuesUrl + text, function (issues) { + return cb(issues); + }); + }, + lookup: 'label', + fillAttr: 'label', + requireLeadingSpace: true, + selectTemplate: function (issue) { + return '#' + issue.original.id; + }, + noMatchTemplate: function () { + return ''; } - remoteSearch(issuesUrl + text, function (issues) { - return cb(issues); - }); - }, - lookup: 'label', - fillAttr: 'label', - requireLeadingSpace: true, - selectTemplate: function (issue) { - return '#' + issue.original.id; - }, - noMatchTemplate: function () { - return ''; } - }); + const AutoCompleteWikiPageTitle = + { + trigger: '[[', + values: function (text, cb) { + remoteSearch(element.dataset.wikiPagesUrl + text, function (wikiPages) { + return cb(wikiPages); + }); + }, + lookup: 'label', + fillAttr: 'label', + requireLeadingSpace: true, + selectTemplate: function (wikiPage) { + return '[[' + wikiPage.original.value + ']]'; + }, + noMatchTemplate: function () { + return ''; + } + } + + const tribute = new Tribute({ + collection: [ + AutoCompleteIssueId, + ($(element).closest('.jstEditor').length && element.dataset.wikiPagesUrl ? AutoCompleteWikiPageTitle : {}) + ]}); tribute.attach(element); } - $(document).ready(setupAjaxIndicator); $(document).ready(hideOnLoad); $(document).ready(addFormObserversForDoubleSubmit); diff --git a/test/functional/auto_completes_controller_test.rb b/test/functional/auto_completes_controller_test.rb index bc1f2eba1..3b40e9186 100644 --- a/test/functional/auto_completes_controller_test.rb +++ b/test/functional/auto_completes_controller_test.rb @@ -196,4 +196,69 @@ class AutoCompletesControllerTest < Redmine::ControllerTest assert_equal 10, json.count assert_equal Issue.last.id, json.first['id'].to_i end + + def test_wiki_pages_should_not_be_case_sensitive + get( + :wiki_pages, + params: { + project_id: 'ecookbook', + q: 'pAgE' + } + ) + assert_response :success + assert_include "Page_with_an_inline_image", response.body + end + + def test_wiki_pages_without_project_should_search_all_projects + get(:wiki_pages, params: {q: 'pAgE'}) + assert_response :success + assert_include "Page_with_an_inline_image", response.body # project eCookbook + assert_include "Start_page", response.body # project OnlineStore + end + + def test_wiki_pages_should_return_json + get( + :wiki_pages, + params: { + project_id: 'ecookbook', + q: 'Page_with_an_inline_image' + } + ) + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_kind_of Array, json + issue = json.first + assert_kind_of Hash, issue + assert_equal 4, issue['id'] + assert_equal 'Page_with_an_inline_image', issue['value'] + assert_equal 'Page_with_an_inline_image', issue['label'] + end + + def test_wiki_pages_should_return_json_content_type_response + get( + :wiki_pages, + params: { + project_id: 'ecookbook', + q: 'Page_with_an_inline_image' + } + ) + assert_response :success + assert_include 'application/json', response.headers['Content-Type'] + end + + def test_wiki_pages_without_q_params_should_return_last_10_pages + # There are 8 pages generated by fixtures + # and we need three more to test the 10 limit + wiki = Wiki.find_by(project: Project.first) + 3.times do |n| + WikiPage.create(wiki: wiki, title: "test#{n}") + end + get :wiki_pages, params: { project_id: 'ecookbook' } + + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + + assert_equal 10, json.count + assert_equal wiki.pages[-2].id, json.first['id'].to_i + end end