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