Feature #33820 » feature-33820.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 |
} |
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/custom_fields_helper.rb | ||
---|---|---|
85 | 85 |
css += ' wiki-edit' |
86 | 86 |
data = { |
87 | 87 |
:auto_complete => true, |
88 |
:issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => '') |
|
88 |
:issues_url => auto_complete_issues_path(:project_id => custom_value.customized.project, :q => ''), |
|
89 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => custom_value.customized.project, :q => '') |
|
89 | 90 |
} if custom_value.customized&.try(:project) |
90 | 91 |
end |
91 | 92 |
custom_value.custom_field.format.edit_tag( |
... | ... | |
134 | 135 |
css += ' wiki-edit' |
135 | 136 |
data = { |
136 | 137 |
:auto_complete => true, |
137 |
:issues_url => auto_complete_issues_path(:q => '') |
|
138 |
:issues_url => auto_complete_issues_path(:q => ''), |
|
139 |
:wiki_pages_url => auto_complete_wiki_pages_path(:q => '') |
|
138 | 140 |
} |
139 | 141 |
end |
140 | 142 |
custom_field.format.bulk_edit_tag( |
app/views/documents/_form.html.erb | ||
---|---|---|
6 | 6 |
<p><%= f.text_area :description, :cols => 60, :rows => 15, :class => 'wiki-edit', |
7 | 7 |
:data => { |
8 | 8 |
:auto_complete => true, |
9 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
9 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
10 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
10 | 11 |
} |
11 | 12 |
%></p> |
12 | 13 |
app/views/issues/_edit.html.erb | ||
---|---|---|
32 | 32 |
<%= f.text_area :notes, :cols => 60, :rows => 10, :class => 'wiki-edit', |
33 | 33 |
:data => { |
34 | 34 |
:auto_complete => true, |
35 |
:issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => '') |
|
35 |
:issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => ''), |
|
36 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @issue.project, :q => '') |
|
36 | 37 |
}, |
37 | 38 |
:no_label => true %> |
38 | 39 |
<%= wikitoolbar_for 'issue_notes', preview_issue_path(:project_id => @project, :issue_id => @issue) %> |
app/views/issues/_form.html.erb | ||
---|---|---|
38 | 38 |
:rows => [[10, @issue.description.to_s.length / 50].max, 20].min, |
39 | 39 |
:data => { |
40 | 40 |
:auto_complete => true, |
41 |
:issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => '') |
|
41 |
:issues_url => auto_complete_issues_path(:project_id => @issue.project, :q => ''), |
|
42 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @issue.project, :q => '') |
|
42 | 43 |
}, |
43 | 44 |
:no_label => true %> |
44 | 45 |
<% end %> |
app/views/issues/bulk_edit.html.erb | ||
---|---|---|
222 | 222 |
<%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit', |
223 | 223 |
:data => { |
224 | 224 |
:auto_complete => true, |
225 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
225 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
226 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
226 | 227 |
} |
227 | 228 |
%> |
228 | 229 |
<%= wikitoolbar_for 'notes' %> |
app/views/journals/_notes_form.html.erb | ||
---|---|---|
7 | 7 |
:rows => (@journal.notes.blank? ? 10 : [[10, @journal.notes.length / 50].max, 100].min), |
8 | 8 |
:data => { |
9 | 9 |
:auto_complete => true, |
10 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
10 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
11 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
11 | 12 |
} |
12 | 13 |
%> |
13 | 14 |
<% if @journal.safe_attribute? 'private_notes' %> |
app/views/messages/_form.html.erb | ||
---|---|---|
27 | 27 |
:accesskey => accesskey(:edit), |
28 | 28 |
:data => { |
29 | 29 |
:auto_complete => true, |
30 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
30 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
31 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
31 | 32 |
} |
32 | 33 |
%></p> |
33 | 34 |
<%= wikitoolbar_for 'message_content', preview_board_message_path(:board_id => @board, :id => @message) %> |
app/views/news/_form.html.erb | ||
---|---|---|
6 | 6 |
<p><%= f.text_area :description, :required => true, :cols => 60, :rows => 15, :class => 'wiki-edit', |
7 | 7 |
:data => { |
8 | 8 |
:auto_complete => true, |
9 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
9 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
10 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
10 | 11 |
} |
11 | 12 |
%></p> |
12 | 13 |
<p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @news} %></p> |
app/views/news/show.html.erb | ||
---|---|---|
56 | 56 |
<%= text_area 'comment', 'comments', :cols => 80, :rows => 15, :class => 'wiki-edit', |
57 | 57 |
:data => { |
58 | 58 |
:auto_complete => true, |
59 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
59 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
60 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
60 | 61 |
} |
61 | 62 |
%> |
62 | 63 |
<%= wikitoolbar_for 'comment_comments', preview_news_path(:project_id => @project, :id => @news) %> |
app/views/wiki/edit.html.erb | ||
---|---|---|
17 | 17 |
:class => 'wiki-edit', |
18 | 18 |
:data => { |
19 | 19 |
:auto_complete => true, |
20 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => '') |
|
20 |
:issues_url => auto_complete_issues_path(:project_id => @project, :q => ''), |
|
21 |
:wiki_pages_url => auto_complete_wiki_pages_path(:project_id => @project, :q => '') |
|
21 | 22 |
} |
22 | 23 |
%> |
23 | 24 |
config/routes.rb | ||
---|---|---|
43 | 43 |
post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit' |
44 | 44 |
post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy' |
45 | 45 | |
46 |
# Misc issue routes. TODO: move into resources
|
|
46 |
# Auto complate routes
|
|
47 | 47 |
match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues' |
48 |
match '/wiki_pages/auto_complete', :to => 'auto_completes#wiki_pages', :via => :get, :as => 'auto_complete_wiki_pages' |
|
49 | ||
48 | 50 |
match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu', :via => [:get, :post] |
49 | 51 |
match '/issues/changes', :to => 'journals#index', :as => 'issue_changes', :via => :get |
50 | 52 |
match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue' |
lib/redmine.rb | ||
---|---|---|
154 | 154 |
end |
155 | 155 | |
156 | 156 |
map.project_module :wiki do |map| |
157 |
map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true |
|
157 |
map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index], :auto_complete => [:wiki_pages]}, :read => true
|
|
158 | 158 |
map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true |
159 | 159 |
map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true |
160 | 160 |
map.permission :edit_wiki_pages, :wiki => [:new, :edit, :update, :preview, :add_attachment], :attachments => :upload |
public/javascripts/application.js | ||
---|---|---|
1040 | 1040 |
// do not attach if Tribute is already initialized |
1041 | 1041 |
if (element.dataset.tribute === 'true') {return;} |
1042 | 1042 | |
1043 |
const issuesUrl = element.dataset.issuesUrl; |
|
1044 |
const usersUrl = element.dataset.usersUrl; |
|
1045 | ||
1046 | 1043 |
const remoteSearch = function(url, cb) { |
1047 | 1044 |
const xhr = new XMLHttpRequest(); |
1048 | 1045 |
xhr.onreadystatechange = function () |
... | ... | |
1060 | 1057 |
xhr.send(); |
1061 | 1058 |
}; |
1062 | 1059 | |
1063 |
const tribute = new Tribute({ |
|
1064 |
trigger: '#', |
|
1065 |
values: function (text, cb) { |
|
1066 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1067 |
$(element).attr('autocomplete', 'off'); |
|
1060 |
const AutoCompleteIssueId = |
|
1061 |
{ |
|
1062 |
trigger: '#', |
|
1063 |
values: function (text, cb) { |
|
1064 |
if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') { |
|
1065 |
$(element).attr('autocomplete', 'off'); |
|
1066 |
} |
|
1067 |
remoteSearch(element.dataset.issuesUrl + text, function (issues) { |
|
1068 |
return cb(issues); |
|
1069 |
}); |
|
1070 |
}, |
|
1071 |
lookup: 'label', |
|
1072 |
fillAttr: 'label', |
|
1073 |
requireLeadingSpace: true, |
|
1074 |
selectTemplate: function (issue) { |
|
1075 |
return '#' + issue.original.id; |
|
1076 |
}, |
|
1077 |
noMatchTemplate: function () { |
|
1078 |
return '<span style:"visibility: hidden;"></span>'; |
|
1068 | 1079 |
} |
1069 |
remoteSearch(issuesUrl + text, function (issues) { |
|
1070 |
return cb(issues); |
|
1071 |
}); |
|
1072 |
}, |
|
1073 |
lookup: 'label', |
|
1074 |
fillAttr: 'label', |
|
1075 |
requireLeadingSpace: true, |
|
1076 |
selectTemplate: function (issue) { |
|
1077 |
return '#' + issue.original.id; |
|
1078 |
}, |
|
1079 |
noMatchTemplate: function () { |
|
1080 |
return '<span style:"visibility: hidden;"></span>'; |
|
1081 | 1080 |
} |
1082 |
}); |
|
1081 |
const AutoCompleteWikiPageTitle = |
|
1082 |
{ |
|
1083 |
trigger: '[[', |
|
1084 |
values: function (text, cb) { |
|
1085 |
remoteSearch(element.dataset.wikiPagesUrl + text, function (wikiPages) { |
|
1086 |
return cb(wikiPages); |
|
1087 |
}); |
|
1088 |
}, |
|
1089 |
lookup: 'label', |
|
1090 |
fillAttr: 'label', |
|
1091 |
requireLeadingSpace: true, |
|
1092 |
selectTemplate: function (wikiPage) { |
|
1093 |
return '[[' + wikiPage.original.value + ']]'; |
|
1094 |
}, |
|
1095 |
noMatchTemplate: function () { |
|
1096 |
return '<span style:"visibility: hidden;"></span>'; |
|
1097 |
} |
|
1098 |
} |
|
1099 | ||
1100 |
const tribute = new Tribute({ |
|
1101 |
collection: [ |
|
1102 |
AutoCompleteIssueId, |
|
1103 |
($(element).closest('.jstEditor').length && element.dataset.wikiPagesUrl ? AutoCompleteWikiPageTitle : {}) |
|
1104 |
]}); |
|
1083 | 1105 | |
1084 | 1106 |
tribute.attach(element); |
1085 | 1107 |
} |
1086 | 1108 | |
1087 | ||
1088 | 1109 |
$(document).ready(setupAjaxIndicator); |
1089 | 1110 |
$(document).ready(hideOnLoad); |
1090 | 1111 |
$(document).ready(addFormObserversForDoubleSubmit); |
test/functional/auto_completes_controller_test.rb | ||
---|---|---|
196 | 196 |
assert_equal 10, json.count |
197 | 197 |
assert_equal Issue.last.id, json.first['id'].to_i |
198 | 198 |
end |
199 | ||
200 |
def test_wiki_pages_should_not_be_case_sensitive |
|
201 |
get( |
|
202 |
:wiki_pages, |
|
203 |
params: { |
|
204 |
project_id: 'ecookbook', |
|
205 |
q: 'pAgE' |
|
206 |
} |
|
207 |
) |
|
208 |
assert_response :success |
|
209 |
assert_include "Page_with_an_inline_image", response.body |
|
210 |
end |
|
211 | ||
212 |
def test_wiki_pages_without_project_should_search_all_projects |
|
213 |
get(:wiki_pages, params: {q: 'pAgE'}) |
|
214 |
assert_response :success |
|
215 |
assert_include "Page_with_an_inline_image", response.body # project eCookbook |
|
216 |
assert_include "Start_page", response.body # project OnlineStore |
|
217 |
end |
|
218 | ||
219 |
def test_wiki_pages_should_return_json |
|
220 |
get( |
|
221 |
:wiki_pages, |
|
222 |
params: { |
|
223 |
project_id: 'ecookbook', |
|
224 |
q: 'Page_with_an_inline_image' |
|
225 |
} |
|
226 |
) |
|
227 |
assert_response :success |
|
228 |
json = ActiveSupport::JSON.decode(response.body) |
|
229 |
assert_kind_of Array, json |
|
230 |
issue = json.first |
|
231 |
assert_kind_of Hash, issue |
|
232 |
assert_equal 4, issue['id'] |
|
233 |
assert_equal 'Page_with_an_inline_image', issue['value'] |
|
234 |
assert_equal 'Page_with_an_inline_image', issue['label'] |
|
235 |
end |
|
236 | ||
237 |
def test_wiki_pages_should_return_json_content_type_response |
|
238 |
get( |
|
239 |
:wiki_pages, |
|
240 |
params: { |
|
241 |
project_id: 'ecookbook', |
|
242 |
q: 'Page_with_an_inline_image' |
|
243 |
} |
|
244 |
) |
|
245 |
assert_response :success |
|
246 |
assert_include 'application/json', response.headers['Content-Type'] |
|
247 |
end |
|
248 | ||
249 |
def test_wiki_pages_without_q_params_should_return_last_10_pages |
|
250 |
# There are 8 pages generated by fixtures |
|
251 |
# and we need three more to test the 10 limit |
|
252 |
wiki = Wiki.find_by(project: Project.first) |
|
253 |
3.times do |n| |
|
254 |
WikiPage.create(wiki: wiki, title: "test#{n}") |
|
255 |
end |
|
256 |
get :wiki_pages, params: { project_id: 'ecookbook' } |
|
257 | ||
258 |
assert_response :success |
|
259 |
json = ActiveSupport::JSON.decode(response.body) |
|
260 | ||
261 |
assert_equal 10, json.count |
|
262 |
assert_equal wiki.pages[-2].id, json.first['id'].to_i |
|
263 |
end |
|
199 | 264 |
end |