From 2f3222e0bdaacae4a2d17224d61a18cb060e9031 Mon Sep 17 00:00:00 2001 From: Go MAEDA Date: Thu, 25 Feb 2021 04:07:37 +0000 Subject: [PATCH] Auto complete wiki page links (#33820). Patch by Mizuki ISHIKAWA. git-svn-id: http://svn.redmine.org/redmine/trunk@20755 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/auto_completes_controller.rb | 28 +++++++ app/helpers/application_helper.rb | 3 +- config/routes.rb | 5 +- lib/redmine.rb | 2 +- public/javascripts/application.js | 55 +++++++++----- .../auto_completes_controller_test.rb | 75 ++++++++++++++++++- test/system/inline_autocomplete_test.rb | 24 +++++- 7 files changed, 170 insertions(+), 22 deletions(-) diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb index cd6434fa4..f4b0f1fd2 100644 --- a/app/controllers/auto_completes_controller.rb +++ b/app/controllers/auto_completes_controller.rb @@ -42,6 +42,24 @@ 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) + if wiki.nil? || !User.current.allowed_to?(:view_wiki_pages, @project) + render json: [] + return + end + + scope = wiki.pages.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 +79,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 45ce68988..deb977b4f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1804,7 +1804,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 acc3ca465..cb6f7ec34 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 409da528a..2d4dd0f7d 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 feabd9ca9..770d5900c 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1113,24 +1113,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 bc1f2eba1..f51ff5bf5 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,76 @@ 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_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_without_view_wiki_pages_permission_should_not_return_pages + Role.anonymous.remove_permission! :view_wiki_pages + get :wiki_pages, params: { project_id: 'ecookbook', q: 'Page_with_an_inline_image' } + + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_equal 0, json.count + end + + def test_wiki_pages_without_project_id_params_should_not_return_pages + get :wiki_pages, params: { project_id: '' } + assert_response :success + json = ActiveSupport::JSON.decode(response.body) + assert_equal 0, json.count + 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 diff --git a/test/system/inline_autocomplete_test.rb b/test/system/inline_autocomplete_test.rb index 7d557f4c6..f3c7daef6 100644 --- a/test/system/inline_autocomplete_test.rb +++ b/test/system/inline_autocomplete_test.rb @@ -24,7 +24,7 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, :enumerations, :custom_fields, :custom_values, :custom_fields_trackers, :watchers, :journals, :journal_details, :versions, - :workflows + :workflows, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions def test_inline_autocomplete_for_issues log_user('jsmith', 'jsmith') @@ -129,4 +129,26 @@ class InlineAutocompleteSystemTest < ApplicationSystemTestCase page.has_css?('.tribute-container li', minimum: 1) end + + def test_inline_autocompletion_of_wiki_page_links + log_user('jsmith', 'jsmith') + visit 'issues/new' + + fill_in 'Description', :with => '[[' + + within('.tribute-container') do + assert page.has_text? 'Child_1_1' + assert page.has_text? 'Page_with_sections' + end + + fill_in 'Description', :with => '[[page' + within('.tribute-container') do + assert page.has_text? 'Page_with_sections' + assert page.has_text? 'Another_page' + assert_not page.has_text? 'Child_1_1' + + first('li').click + end + assert_equal '[[Page_with_sections]] ', find('#issue_description').value + end end -- 2.39.5