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 |