diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb index cd6434fa4a0f1dd2be0f7ba246444afacaf087e5..7f93d28885d5db0ca148d85c5a3c525dada07a01 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 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/application_helper.rb b/app/helpers/application_helper.rb index dd4a4f195ec2066209c5d9721c46fe8f7a5c2949..b8ba1566a7a652f485d04a80bbac7f7ae5be06ce 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1743,7 +1743,8 @@ module ApplicationHelper def autocomplete_data_sources(project) { - issues: auto_complete_issues_path(:project_id => project, :q => '') + issues: auto_complete_issues_path(:project_id => project, :q => ''), + wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => '') } end diff --git a/config/routes.rb b/config/routes.rb index acc3ca4651175c90286b1d2db900a58e4de9bd4f..cb6f7ec34a323a7ad49181491980740fe1aa8ef4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,8 +46,11 @@ 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' + + # Misc issue routes. TODO: move into resources 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 9eaa64ff30780f1e297481ca47537ea102b4a983..37424f10f186f37954e6d8b9d7589a693ae45d33 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -162,7 +162,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 c9b7d943d37b80bf8d6396cc2f466a3a1b0a4c1a..95b6e47a6300e5a5a833c5f9eb4c5b57ab7d69a2 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1080,24 +1080,45 @@ function inlineAutoComplete(element) { }; const tribute = new Tribute({ - trigger: '#', - values: function (text, cb) { - if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { - $(element).attr('autocomplete', 'off'); + collection: [ + { + trigger: '#', + values: function (text, cb) { + if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { + $(element).attr('autocomplete', 'off'); + } + remoteSearch(getDataSource('issues') + text, function (issues) { + return cb(issues); + }); + }, + lookup: 'label', + fillAttr: 'label', + requireLeadingSpace: true, + selectTemplate: function (issue) { + return '#' + issue.original.id; + }, + noMatchTemplate: function () { + return ''; + } + }, + { + trigger: '[[', + values: function (text, cb) { + remoteSearch(getDataSource('wiki_pages') + text, function (wikiPages) { + return cb(wikiPages); + }); + }, + lookup: 'label', + fillAttr: 'label', + requireLeadingSpace: true, + selectTemplate: function (wikiPage) { + return '[[' + wikiPage.original.value + ']]'; + }, + noMatchTemplate: function () { + return ''; + } } - remoteSearch(getDataSource('issues') + text, function (issues) { - return cb(issues); - }); - }, - lookup: 'label', - fillAttr: 'label', - requireLeadingSpace: true, - selectTemplate: function (issue) { - return '#' + issue.original.id; - }, - noMatchTemplate: function () { - return ''; - } + ] }); tribute.attach(element); diff --git a/test/functional/auto_completes_controller_test.rb b/test/functional/auto_completes_controller_test.rb index bc1f2eba176d0e5a9cfdd119a2c1d36bb33cca44..dd57b6c4c850c3e7549225211db34248f489a983 100644 --- a/test/functional/auto_completes_controller_test.rb +++ b/test/functional/auto_completes_controller_test.rb @@ -28,7 +28,8 @@ class AutoCompletesControllerTest < Redmine::ControllerTest :member_roles, :members, :enabled_modules, - :journals, :journal_details + :journals, :journal_details, + :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions def test_issues_should_not_be_case_sensitive get( @@ -196,4 +197,67 @@ 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