]> source.dussan.org Git - redmine.git/commitdiff
Auto complete wiki page links (#33820).
authorGo MAEDA <maeda@farend.jp>
Thu, 25 Feb 2021 04:07:37 +0000 (04:07 +0000)
committerGo MAEDA <maeda@farend.jp>
Thu, 25 Feb 2021 04:07:37 +0000 (04:07 +0000)
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
app/helpers/application_helper.rb
config/routes.rb
lib/redmine.rb
public/javascripts/application.js
test/functional/auto_completes_controller_test.rb
test/system/inline_autocomplete_test.rb

index cd6434fa4a0f1dd2be0f7ba246444afacaf087e5..f4b0f1fd26614cedba9c6cac99f6e135e401552b 100644 (file)
@@ -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
index 45ce689880c3a99be3a3e3fa9847fccca5517007..deb977b4f42f35ee74f4b3b36cac982ac8f3c2b5 100644 (file)
@@ -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
 
index acc3ca4651175c90286b1d2db900a58e4de9bd4f..cb6f7ec34a323a7ad49181491980740fe1aa8ef4 100644 (file)
@@ -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'
index 409da528a57f6171d97c2652c57befa46e8419dc..2d4dd0f7dc6f864d9a725b58b3de71bb8f764d35 100644 (file)
@@ -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
index feabd9ca9ff6105ce31e02ce1a95cbbc5d720d62..770d5900cbf683a73956cc368996fb5184e95a45 100644 (file)
@@ -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 '<span style:"visibility: hidden;"></span>';
+          }
+        },
+        {
+          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 '<span style:"visibility: hidden;"></span>';
+          }
         }
-        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 '<span style:"visibility: hidden;"></span>';
-      }
+      ]
     });
 
     tribute.attach(element);
index bc1f2eba176d0e5a9cfdd119a2c1d36bb33cca44..f51ff5bf5529e3b7fefac44f7b48179ef22d4a48 100644 (file)
@@ -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
index 7d557f4c6d36453da306634a1c6c4bcf6666693c..f3c7daef6ba1901478540cba1df37cc10a21f233 100644 (file)
@@ -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