Feature #33820 » feature-33820-v3-with-test.patch
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 | ||
---|---|---|
1804 | 1804 | |
1805 | 1805 |
def autocomplete_data_sources(project) |
1806 | 1806 |
{ |
1807 |
issues: auto_complete_issues_path(:project_id => project, :q => '') |
|
1807 |
issues: auto_complete_issues_path(:project_id => project, :q => ''), |
|
1808 |
wiki_pages: auto_complete_wiki_pages_path(:project_id => project, :q => '') |
|
1808 | 1809 |
} |
1809 | 1810 |
end |
1810 | 1811 |
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 | ||
---|---|---|
1113 | 1113 |
}; |
1114 | 1114 | |
1115 | 1115 |
const tribute = new Tribute({ |
1116 |
trigger: '#', |
|
1117 |
values: function (text, cb) { |
|
1118 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1119 |
$(element).attr('autocomplete', 'off'); |
|
1116 |
collection: [ |
|
1117 |
{ |
|
1118 |
trigger: '#', |
|
1119 |
values: function (text, cb) { |
|
1120 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1121 |
$(element).attr('autocomplete', 'off'); |
|
1122 |
} |
|
1123 |
remoteSearch(getDataSource('issues') + text, function (issues) { |
|
1124 |
return cb(issues); |
|
1125 |
}); |
|
1126 |
}, |
|
1127 |
lookup: 'label', |
|
1128 |
fillAttr: 'label', |
|
1129 |
requireLeadingSpace: true, |
|
1130 |
selectTemplate: function (issue) { |
|
1131 |
return '#' + issue.original.id; |
|
1132 |
}, |
|
1133 |
noMatchTemplate: function () { |
|
1134 |
return '<span style:"visibility: hidden;"></span>'; |
|
1135 |
} |
|
1136 |
}, |
|
1137 |
{ |
|
1138 |
trigger: '[[', |
|
1139 |
values: function (text, cb) { |
|
1140 |
remoteSearch(getDataSource('wiki_pages') + text, function (wikiPages) { |
|
1141 |
return cb(wikiPages); |
|
1142 |
}); |
|
1143 |
}, |
|
1144 |
lookup: 'label', |
|
1145 |
fillAttr: 'label', |
|
1146 |
requireLeadingSpace: true, |
|
1147 |
selectTemplate: function (wikiPage) { |
|
1148 |
return '[[' + wikiPage.original.value + ']]'; |
|
1149 |
}, |
|
1150 |
noMatchTemplate: function () { |
|
1151 |
return '<span style:"visibility: hidden;"></span>'; |
|
1152 |
} |
|
1120 | 1153 |
} |
1121 |
remoteSearch(getDataSource('issues') + text, function (issues) { |
|
1122 |
return cb(issues); |
|
1123 |
}); |
|
1124 |
}, |
|
1125 |
lookup: 'label', |
|
1126 |
fillAttr: 'label', |
|
1127 |
requireLeadingSpace: true, |
|
1128 |
selectTemplate: function (issue) { |
|
1129 |
return '#' + issue.original.id; |
|
1130 |
}, |
|
1131 |
noMatchTemplate: function () { |
|
1132 |
return '<span style:"visibility: hidden;"></span>'; |
|
1133 |
} |
|
1154 |
] |
|
1134 | 1155 |
}); |
1135 | 1156 | |
1136 | 1157 |
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 |
test/system/inline_autocomplete_test.rb | ||
---|---|---|
24 | 24 |
:trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues, |
25 | 25 |
:enumerations, :custom_fields, :custom_values, :custom_fields_trackers, |
26 | 26 |
:watchers, :journals, :journal_details, :versions, |
27 |
:workflows |
|
27 |
:workflows, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
|
|
28 | 28 | |
29 | 29 |
def test_inline_autocomplete_for_issues |
30 | 30 |
log_user('jsmith', 'jsmith') |
... | ... | |
129 | 129 | |
130 | 130 |
page.has_css?('.tribute-container li', minimum: 1) |
131 | 131 |
end |
132 | ||
133 |
def test_inline_autocompletion_of_wiki_page_links |
|
134 |
log_user('jsmith', 'jsmith') |
|
135 |
visit 'issues/new' |
|
136 | ||
137 |
fill_in 'Description', :with => '[[' |
|
138 | ||
139 |
within('.tribute-container') do |
|
140 |
assert page.has_text? 'Child_1_1' |
|
141 |
assert page.has_text? 'Page_with_sections' |
|
142 |
end |
|
143 | ||
144 |
fill_in 'Description', :with => '[[page' |
|
145 |
within('.tribute-container') do |
|
146 |
assert page.has_text? 'Page_with_sections' |
|
147 |
assert page.has_text? 'Another_page' |
|
148 |
assert_not page.has_text? 'Child_1_1' |
|
149 | ||
150 |
first('li').click |
|
151 |
end |
|
152 |
assert_equal '[[Page_with_sections]] ', find('#issue_description').value |
|
153 |
end |
|
132 | 154 |
end |