Feature #33820 » feature-33820-v2.diff
app/controllers/auto_completes_controller.rb | ||
---|---|---|
42 | 42 |
render :json => format_issues_json(issues) |
43 | 43 |
end |
44 | 44 | |
45 |
def wiki_pages |
|
46 |
q = params[:q].to_s.strip |
|
47 |
wiki = Wiki.find_by(project: @project) |
|
48 |
scope = (@project && wiki ? wiki.pages : WikiPage.all).reorder(id: :desc) |
|
49 |
wiki_pages = |
|
50 |
if q.present? |
|
51 |
scope.where("LOWER(#{WikiPage.table_name}.title) LIKE LOWER(?)", "%#{q}%").limit(10).to_a |
|
52 |
else |
|
53 |
scope.limit(10).to_a |
|
54 |
end |
|
55 |
render :json => format_wiki_pages_json(wiki_pages) |
|
56 |
end |
|
57 | ||
45 | 58 |
private |
46 | 59 | |
47 | 60 |
def find_project |
... | ... | |
61 | 74 |
} |
62 | 75 |
end |
63 | 76 |
end |
77 | ||
78 |
def format_wiki_pages_json(wiki_pages) |
|
79 |
wiki_pages.map {|wiki_page| |
|
80 |
{ |
|
81 |
'id' => wiki_page.id, |
|
82 |
'label' => wiki_page.title.to_s.truncate(255), |
|
83 |
'value' => wiki_page.title |
|
84 |
} |
|
85 |
} |
|
86 |
end |
|
64 | 87 |
end |
app/helpers/application_helper.rb | ||
---|---|---|
1743 | 1743 | |
1744 | 1744 |
def autocomplete_data_sources(project) |
1745 | 1745 |
{ |
1746 |
issues: auto_complete_issues_path(:project_id => project, :q => '') |
|
1746 |
issues: auto_complete_issues_path(:project_id => project, :q => ''), |
|
1747 |
wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => '') |
|
1747 | 1748 |
} |
1748 | 1749 |
end |
1749 | 1750 |
config/routes.rb | ||
---|---|---|
46 | 46 |
post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
47 | 47 |
post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' |
48 | 48 | |
49 |
# Misc issue routes. TODO: move into resources
|
|
49 |
# Auto complate routes
|
|
50 | 50 |
match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' |
51 |
match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages' |
|
52 | ||
53 |
# Misc issue routes. TODO: move into resources |
|
51 | 54 |
match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] |
52 | 55 |
match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get |
53 | 56 |
match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' |
lib/redmine.rb | ||
---|---|---|
162 | 162 |
end |
163 | 163 | |
164 | 164 |
map.project_module :wiki do |map| |
165 |
map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true |
|
165 |
map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index], :auto_complete => [:wiki_pages]}, :read => true
|
|
166 | 166 |
map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true |
167 | 167 |
map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true |
168 | 168 |
map.permission :edit_wiki_pages, :wiki => [:new, :edit, :update, :preview, :add_attachment], :attachments => :upload |
public/javascripts/application.js | ||
---|---|---|
1080 | 1080 |
}; |
1081 | 1081 | |
1082 | 1082 |
const tribute = new Tribute({ |
1083 |
trigger: '#', |
|
1084 |
values: function (text, cb) { |
|
1085 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1086 |
$(element).attr('autocomplete', 'off'); |
|
1083 |
collection: [ |
|
1084 |
{ |
|
1085 |
trigger: '#', |
|
1086 |
values: function (text, cb) { |
|
1087 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1088 |
$(element).attr('autocomplete', 'off'); |
|
1089 |
} |
|
1090 |
remoteSearch(getDataSource('issues') + text, function (issues) { |
|
1091 |
return cb(issues); |
|
1092 |
}); |
|
1093 |
}, |
|
1094 |
lookup: 'label', |
|
1095 |
fillAttr: 'label', |
|
1096 |
requireLeadingSpace: true, |
|
1097 |
selectTemplate: function (issue) { |
|
1098 |
return '#' + issue.original.id; |
|
1099 |
}, |
|
1100 |
noMatchTemplate: function () { |
|
1101 |
return '<span style:"visibility: hidden;"></span>'; |
|
1102 |
} |
|
1103 |
}, |
|
1104 |
{ |
|
1105 |
trigger: '[[', |
|
1106 |
values: function (text, cb) { |
|
1107 |
remoteSearch(getDataSource('wiki_pages') + text, function (wikiPages) { |
|
1108 |
return cb(wikiPages); |
|
1109 |
}); |
|
1110 |
}, |
|
1111 |
lookup: 'label', |
|
1112 |
fillAttr: 'label', |
|
1113 |
requireLeadingSpace: true, |
|
1114 |
selectTemplate: function (wikiPage) { |
|
1115 |
return '[[' + wikiPage.original.value + ']]'; |
|
1116 |
}, |
|
1117 |
noMatchTemplate: function () { |
|
1118 |
return '<span style:"visibility: hidden;"></span>'; |
|
1119 |
} |
|
1087 | 1120 |
} |
1088 |
remoteSearch(getDataSource('issues') + text, function (issues) { |
|
1089 |
return cb(issues); |
|
1090 |
}); |
|
1091 |
}, |
|
1092 |
lookup: 'label', |
|
1093 |
fillAttr: 'label', |
|
1094 |
requireLeadingSpace: true, |
|
1095 |
selectTemplate: function (issue) { |
|
1096 |
return '#' + issue.original.id; |
|
1097 |
}, |
|
1098 |
noMatchTemplate: function () { |
|
1099 |
return '<span style:"visibility: hidden;"></span>'; |
|
1100 |
} |
|
1121 |
] |
|
1101 | 1122 |
}); |
1102 | 1123 | |
1103 | 1124 |
tribute.attach(element); |
test/functional/auto_completes_controller_test.rb | ||
---|---|---|
28 | 28 |
:member_roles, |
29 | 29 |
:members, |
30 | 30 |
:enabled_modules, |
31 |
:journals, :journal_details |
|
31 |
:journals, :journal_details, |
|
32 |
:wikis, :wiki_pages, :wiki_contents, :wiki_content_versions |
|
32 | 33 | |
33 | 34 |
def test_issues_should_not_be_case_sensitive |
34 | 35 |
get( |
... | ... | |
196 | 197 |
assert_equal 10, json.count |
197 | 198 |
assert_equal Issue.last.id, json.first['id'].to_i |
198 | 199 |
end |
200 | ||
201 |
def test_wiki_pages_should_not_be_case_sensitive |
|
202 |
get( |
|
203 |
:wiki_pages, |
|
204 |
params: { |
|
205 |
project_id: 'ecookbook', |
|
206 |
q: 'pAgE' |
|
207 |
} |
|
208 |
) |
|
209 |
assert_response :success |
|
210 |
assert_include "Page_with_an_inline_image", response.body |
|
211 |
end |
|
212 | ||
213 |
def test_wiki_pages_without_project_should_search_all_projects |
|
214 |
get(:wiki_pages, params: {q: 'pAgE'}) |
|
215 |
assert_response :success |
|
216 |
assert_include "Page_with_an_inline_image", response.body # project eCookbook |
|
217 |
assert_include "Start_page", response.body # project OnlineStore |
|
218 |
end |
|
219 | ||
220 |
def test_wiki_pages_should_return_json |
|
221 |
get( |
|
222 |
:wiki_pages, |
|
223 |
params: { |
|
224 |
project_id: 'ecookbook', |
|
225 |
q: 'Page_with_an_inline_image' |
|
226 |
} |
|
227 |
) |
|
228 |
assert_response :success |
|
229 |
json = ActiveSupport::JSON.decode(response.body) |
|
230 |
assert_kind_of Array, json |
|
231 |
issue = json.first |
|
232 |
assert_kind_of Hash, issue |
|
233 |
assert_equal 4, issue['id'] |
|
234 |
assert_equal 'Page_with_an_inline_image', issue['value'] |
|
235 |
assert_equal 'Page_with_an_inline_image', issue['label'] |
|
236 |
end |
|
237 | ||
238 |
def test_wiki_pages_should_return_json_content_type_response |
|
239 |
get( |
|
240 |
:wiki_pages, |
|
241 |
params: { |
|
242 |
project_id: 'ecookbook', |
|
243 |
q: 'Page_with_an_inline_image' |
|
244 |
} |
|
245 |
) |
|
246 |
assert_response :success |
|
247 |
assert_include 'application/json', response.headers['Content-Type'] |
|
248 |
end |
|
249 | ||
250 |
def test_wiki_pages_without_q_params_should_return_last_10_pages |
|
251 |
# There are 8 pages generated by fixtures |
|
252 |
# and we need three more to test the 10 limit |
|
253 |
wiki = Wiki.find_by(project: Project.first) |
|
254 |
3.times do |n| |
|
255 |
WikiPage.create(wiki: wiki, title: "test#{n}") |
|
256 |
end |
|
257 |
get :wiki_pages, params: { project_id: 'ecookbook' } |
|
258 |
assert_response :success |
|
259 |
json = ActiveSupport::JSON.decode(response.body) |
|
260 |
assert_equal 10, json.count |
|
261 |
assert_equal wiki.pages[-2].id, json.first['id'].to_i |
|
262 |
end |
|
199 | 263 |
end |