@@ -1,1269 +1,1274 | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | # |
|
2 | # | |
3 | # Redmine - project management software |
|
3 | # Redmine - project management software | |
4 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
4 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; either version 2 |
|
8 | # as published by the Free Software Foundation; either version 2 | |
9 | # of the License, or (at your option) any later version. |
|
9 | # of the License, or (at your option) any later version. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
19 |
|
19 | |||
20 | require 'forwardable' |
|
20 | require 'forwardable' | |
21 | require 'cgi' |
|
21 | require 'cgi' | |
22 |
|
22 | |||
23 | module ApplicationHelper |
|
23 | module ApplicationHelper | |
24 | include Redmine::WikiFormatting::Macros::Definitions |
|
24 | include Redmine::WikiFormatting::Macros::Definitions | |
25 | include Redmine::I18n |
|
25 | include Redmine::I18n | |
26 | include GravatarHelper::PublicMethods |
|
26 | include GravatarHelper::PublicMethods | |
27 |
|
27 | |||
28 | extend Forwardable |
|
28 | extend Forwardable | |
29 | def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter |
|
29 | def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter | |
30 |
|
30 | |||
31 | # Return true if user is authorized for controller/action, otherwise false |
|
31 | # Return true if user is authorized for controller/action, otherwise false | |
32 | def authorize_for(controller, action) |
|
32 | def authorize_for(controller, action) | |
33 | User.current.allowed_to?({:controller => controller, :action => action}, @project) |
|
33 | User.current.allowed_to?({:controller => controller, :action => action}, @project) | |
34 | end |
|
34 | end | |
35 |
|
35 | |||
36 | # Display a link if user is authorized |
|
36 | # Display a link if user is authorized | |
37 | # |
|
37 | # | |
38 | # @param [String] name Anchor text (passed to link_to) |
|
38 | # @param [String] name Anchor text (passed to link_to) | |
39 | # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized |
|
39 | # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized | |
40 | # @param [optional, Hash] html_options Options passed to link_to |
|
40 | # @param [optional, Hash] html_options Options passed to link_to | |
41 | # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to |
|
41 | # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to | |
42 | def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) |
|
42 | def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) | |
43 | link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) |
|
43 | link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) | |
44 | end |
|
44 | end | |
45 |
|
45 | |||
46 | # Displays a link to user's account page if active |
|
46 | # Displays a link to user's account page if active | |
47 | def link_to_user(user, options={}) |
|
47 | def link_to_user(user, options={}) | |
48 | if user.is_a?(User) |
|
48 | if user.is_a?(User) | |
49 | name = h(user.name(options[:format])) |
|
49 | name = h(user.name(options[:format])) | |
50 | if user.active? |
|
50 | if user.active? | |
51 | link_to name, :controller => 'users', :action => 'show', :id => user |
|
51 | link_to name, :controller => 'users', :action => 'show', :id => user | |
52 | else |
|
52 | else | |
53 | name |
|
53 | name | |
54 | end |
|
54 | end | |
55 | else |
|
55 | else | |
56 | h(user.to_s) |
|
56 | h(user.to_s) | |
57 | end |
|
57 | end | |
58 | end |
|
58 | end | |
59 |
|
59 | |||
60 | # Displays a link to +issue+ with its subject. |
|
60 | # Displays a link to +issue+ with its subject. | |
61 | # Examples: |
|
61 | # Examples: | |
62 | # |
|
62 | # | |
63 | # link_to_issue(issue) # => Defect #6: This is the subject |
|
63 | # link_to_issue(issue) # => Defect #6: This is the subject | |
64 | # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... |
|
64 | # link_to_issue(issue, :truncate => 6) # => Defect #6: This i... | |
65 | # link_to_issue(issue, :subject => false) # => Defect #6 |
|
65 | # link_to_issue(issue, :subject => false) # => Defect #6 | |
66 | # link_to_issue(issue, :project => true) # => Foo - Defect #6 |
|
66 | # link_to_issue(issue, :project => true) # => Foo - Defect #6 | |
67 | # |
|
67 | # | |
68 | def link_to_issue(issue, options={}) |
|
68 | def link_to_issue(issue, options={}) | |
69 | title = nil |
|
69 | title = nil | |
70 | subject = nil |
|
70 | subject = nil | |
71 | if options[:subject] == false |
|
71 | if options[:subject] == false | |
72 | title = truncate(issue.subject, :length => 60) |
|
72 | title = truncate(issue.subject, :length => 60) | |
73 | else |
|
73 | else | |
74 | subject = issue.subject |
|
74 | subject = issue.subject | |
75 | if options[:truncate] |
|
75 | if options[:truncate] | |
76 | subject = truncate(subject, :length => options[:truncate]) |
|
76 | subject = truncate(subject, :length => options[:truncate]) | |
77 | end |
|
77 | end | |
78 | end |
|
78 | end | |
79 | s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, |
|
79 | s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, | |
80 | :class => issue.css_classes, |
|
80 | :class => issue.css_classes, | |
81 | :title => title |
|
81 | :title => title | |
82 | s << h(": #{subject}") if subject |
|
82 | s << h(": #{subject}") if subject | |
83 | s = h("#{issue.project} - ") + s if options[:project] |
|
83 | s = h("#{issue.project} - ") + s if options[:project] | |
84 | s |
|
84 | s | |
85 | end |
|
85 | end | |
86 |
|
86 | |||
87 | # Generates a link to an attachment. |
|
87 | # Generates a link to an attachment. | |
88 | # Options: |
|
88 | # Options: | |
89 | # * :text - Link text (default to attachment filename) |
|
89 | # * :text - Link text (default to attachment filename) | |
90 | # * :download - Force download (default: false) |
|
90 | # * :download - Force download (default: false) | |
91 | def link_to_attachment(attachment, options={}) |
|
91 | def link_to_attachment(attachment, options={}) | |
92 | text = options.delete(:text) || attachment.filename |
|
92 | text = options.delete(:text) || attachment.filename | |
93 | action = options.delete(:download) ? 'download' : 'show' |
|
93 | action = options.delete(:download) ? 'download' : 'show' | |
94 | opt_only_path = {} |
|
94 | opt_only_path = {} | |
95 | opt_only_path[:only_path] = (options[:only_path] == false ? false : true) |
|
95 | opt_only_path[:only_path] = (options[:only_path] == false ? false : true) | |
96 | options.delete(:only_path) |
|
96 | options.delete(:only_path) | |
97 | link_to(h(text), |
|
97 | link_to(h(text), | |
98 | {:controller => 'attachments', :action => action, |
|
98 | {:controller => 'attachments', :action => action, | |
99 | :id => attachment, :filename => attachment.filename}.merge(opt_only_path), |
|
99 | :id => attachment, :filename => attachment.filename}.merge(opt_only_path), | |
100 | options) |
|
100 | options) | |
101 | end |
|
101 | end | |
102 |
|
102 | |||
103 | # Generates a link to a SCM revision |
|
103 | # Generates a link to a SCM revision | |
104 | # Options: |
|
104 | # Options: | |
105 | # * :text - Link text (default to the formatted revision) |
|
105 | # * :text - Link text (default to the formatted revision) | |
106 | def link_to_revision(revision, repository, options={}) |
|
106 | def link_to_revision(revision, repository, options={}) | |
107 | if repository.is_a?(Project) |
|
107 | if repository.is_a?(Project) | |
108 | repository = repository.repository |
|
108 | repository = repository.repository | |
109 | end |
|
109 | end | |
110 | text = options.delete(:text) || format_revision(revision) |
|
110 | text = options.delete(:text) || format_revision(revision) | |
111 | rev = revision.respond_to?(:identifier) ? revision.identifier : revision |
|
111 | rev = revision.respond_to?(:identifier) ? revision.identifier : revision | |
112 | link_to( |
|
112 | link_to( | |
113 | h(text), |
|
113 | h(text), | |
114 | {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, |
|
114 | {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev}, | |
115 | :title => l(:label_revision_id, format_revision(revision)) |
|
115 | :title => l(:label_revision_id, format_revision(revision)) | |
116 | ) |
|
116 | ) | |
117 | end |
|
117 | end | |
118 |
|
118 | |||
119 | # Generates a link to a message |
|
119 | # Generates a link to a message | |
120 | def link_to_message(message, options={}, html_options = nil) |
|
120 | def link_to_message(message, options={}, html_options = nil) | |
121 | link_to( |
|
121 | link_to( | |
122 | h(truncate(message.subject, :length => 60)), |
|
122 | h(truncate(message.subject, :length => 60)), | |
123 | { :controller => 'messages', :action => 'show', |
|
123 | { :controller => 'messages', :action => 'show', | |
124 | :board_id => message.board_id, |
|
124 | :board_id => message.board_id, | |
125 | :id => (message.parent_id || message.id), |
|
125 | :id => (message.parent_id || message.id), | |
126 | :r => (message.parent_id && message.id), |
|
126 | :r => (message.parent_id && message.id), | |
127 | :anchor => (message.parent_id ? "message-#{message.id}" : nil) |
|
127 | :anchor => (message.parent_id ? "message-#{message.id}" : nil) | |
128 | }.merge(options), |
|
128 | }.merge(options), | |
129 | html_options |
|
129 | html_options | |
130 | ) |
|
130 | ) | |
131 | end |
|
131 | end | |
132 |
|
132 | |||
133 | # Generates a link to a project if active |
|
133 | # Generates a link to a project if active | |
134 | # Examples: |
|
134 | # Examples: | |
135 | # |
|
135 | # | |
136 | # link_to_project(project) # => link to the specified project overview |
|
136 | # link_to_project(project) # => link to the specified project overview | |
137 | # link_to_project(project, :action=>'settings') # => link to project settings |
|
137 | # link_to_project(project, :action=>'settings') # => link to project settings | |
138 | # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options |
|
138 | # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options | |
139 | # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) |
|
139 | # link_to_project(project, {}, :class => "project") # => html options with default url (project overview) | |
140 | # |
|
140 | # | |
141 | def link_to_project(project, options={}, html_options = nil) |
|
141 | def link_to_project(project, options={}, html_options = nil) | |
142 | if project.archived? |
|
142 | if project.archived? | |
143 | h(project) |
|
143 | h(project) | |
144 | else |
|
144 | else | |
145 | url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) |
|
145 | url = {:controller => 'projects', :action => 'show', :id => project}.merge(options) | |
146 | link_to(h(project), url, html_options) |
|
146 | link_to(h(project), url, html_options) | |
147 | end |
|
147 | end | |
148 | end |
|
148 | end | |
149 |
|
149 | |||
150 | def thumbnail_tag(attachment) |
|
150 | def thumbnail_tag(attachment) | |
151 | link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), |
|
151 | link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), | |
152 | {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, |
|
152 | {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, | |
153 | :title => attachment.filename |
|
153 | :title => attachment.filename | |
154 | end |
|
154 | end | |
155 |
|
155 | |||
156 | def toggle_link(name, id, options={}) |
|
156 | def toggle_link(name, id, options={}) | |
157 | onclick = "$('##{id}').toggle(); " |
|
157 | onclick = "$('##{id}').toggle(); " | |
158 | onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") |
|
158 | onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ") | |
159 | onclick << "return false;" |
|
159 | onclick << "return false;" | |
160 | link_to(name, "#", :onclick => onclick) |
|
160 | link_to(name, "#", :onclick => onclick) | |
161 | end |
|
161 | end | |
162 |
|
162 | |||
163 | def image_to_function(name, function, html_options = {}) |
|
163 | def image_to_function(name, function, html_options = {}) | |
164 | html_options.symbolize_keys! |
|
164 | html_options.symbolize_keys! | |
165 | tag(:input, html_options.merge({ |
|
165 | tag(:input, html_options.merge({ | |
166 | :type => "image", :src => image_path(name), |
|
166 | :type => "image", :src => image_path(name), | |
167 | :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" |
|
167 | :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" | |
168 | })) |
|
168 | })) | |
169 | end |
|
169 | end | |
170 |
|
170 | |||
171 | def format_activity_title(text) |
|
171 | def format_activity_title(text) | |
172 | h(truncate_single_line(text, :length => 100)) |
|
172 | h(truncate_single_line(text, :length => 100)) | |
173 | end |
|
173 | end | |
174 |
|
174 | |||
175 | def format_activity_day(date) |
|
175 | def format_activity_day(date) | |
176 | date == User.current.today ? l(:label_today).titleize : format_date(date) |
|
176 | date == User.current.today ? l(:label_today).titleize : format_date(date) | |
177 | end |
|
177 | end | |
178 |
|
178 | |||
179 | def format_activity_description(text) |
|
179 | def format_activity_description(text) | |
180 | h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') |
|
180 | h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...') | |
181 | ).gsub(/[\r\n]+/, "<br />").html_safe |
|
181 | ).gsub(/[\r\n]+/, "<br />").html_safe | |
182 | end |
|
182 | end | |
183 |
|
183 | |||
184 | def format_version_name(version) |
|
184 | def format_version_name(version) | |
185 | if version.project == @project |
|
185 | if version.project == @project | |
186 | h(version) |
|
186 | h(version) | |
187 | else |
|
187 | else | |
188 | h("#{version.project} - #{version}") |
|
188 | h("#{version.project} - #{version}") | |
189 | end |
|
189 | end | |
190 | end |
|
190 | end | |
191 |
|
191 | |||
192 | def due_date_distance_in_words(date) |
|
192 | def due_date_distance_in_words(date) | |
193 | if date |
|
193 | if date | |
194 | l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) |
|
194 | l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) | |
195 | end |
|
195 | end | |
196 | end |
|
196 | end | |
197 |
|
197 | |||
198 | # Renders a tree of projects as a nested set of unordered lists |
|
198 | # Renders a tree of projects as a nested set of unordered lists | |
199 | # The given collection may be a subset of the whole project tree |
|
199 | # The given collection may be a subset of the whole project tree | |
200 | # (eg. some intermediate nodes are private and can not be seen) |
|
200 | # (eg. some intermediate nodes are private and can not be seen) | |
201 | def render_project_nested_lists(projects) |
|
201 | def render_project_nested_lists(projects) | |
202 | s = '' |
|
202 | s = '' | |
203 | if projects.any? |
|
203 | if projects.any? | |
204 | ancestors = [] |
|
204 | ancestors = [] | |
205 | original_project = @project |
|
205 | original_project = @project | |
206 | projects.sort_by(&:lft).each do |project| |
|
206 | projects.sort_by(&:lft).each do |project| | |
207 | # set the project environment to please macros. |
|
207 | # set the project environment to please macros. | |
208 | @project = project |
|
208 | @project = project | |
209 | if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) |
|
209 | if (ancestors.empty? || project.is_descendant_of?(ancestors.last)) | |
210 | s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n" |
|
210 | s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n" | |
211 | else |
|
211 | else | |
212 | ancestors.pop |
|
212 | ancestors.pop | |
213 | s << "</li>" |
|
213 | s << "</li>" | |
214 | while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) |
|
214 | while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) | |
215 | ancestors.pop |
|
215 | ancestors.pop | |
216 | s << "</ul></li>\n" |
|
216 | s << "</ul></li>\n" | |
217 | end |
|
217 | end | |
218 | end |
|
218 | end | |
219 | classes = (ancestors.empty? ? 'root' : 'child') |
|
219 | classes = (ancestors.empty? ? 'root' : 'child') | |
220 | s << "<li class='#{classes}'><div class='#{classes}'>" |
|
220 | s << "<li class='#{classes}'><div class='#{classes}'>" | |
221 | s << h(block_given? ? yield(project) : project.name) |
|
221 | s << h(block_given? ? yield(project) : project.name) | |
222 | s << "</div>\n" |
|
222 | s << "</div>\n" | |
223 | ancestors << project |
|
223 | ancestors << project | |
224 | end |
|
224 | end | |
225 | s << ("</li></ul>\n" * ancestors.size) |
|
225 | s << ("</li></ul>\n" * ancestors.size) | |
226 | @project = original_project |
|
226 | @project = original_project | |
227 | end |
|
227 | end | |
228 | s.html_safe |
|
228 | s.html_safe | |
229 | end |
|
229 | end | |
230 |
|
230 | |||
231 | def render_page_hierarchy(pages, node=nil, options={}) |
|
231 | def render_page_hierarchy(pages, node=nil, options={}) | |
232 | content = '' |
|
232 | content = '' | |
233 | if pages[node] |
|
233 | if pages[node] | |
234 | content << "<ul class=\"pages-hierarchy\">\n" |
|
234 | content << "<ul class=\"pages-hierarchy\">\n" | |
235 | pages[node].each do |page| |
|
235 | pages[node].each do |page| | |
236 | content << "<li>" |
|
236 | content << "<li>" | |
237 | content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, |
|
237 | content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}, | |
238 | :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) |
|
238 | :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) | |
239 | content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] |
|
239 | content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] | |
240 | content << "</li>\n" |
|
240 | content << "</li>\n" | |
241 | end |
|
241 | end | |
242 | content << "</ul>\n" |
|
242 | content << "</ul>\n" | |
243 | end |
|
243 | end | |
244 | content.html_safe |
|
244 | content.html_safe | |
245 | end |
|
245 | end | |
246 |
|
246 | |||
247 | # Renders flash messages |
|
247 | # Renders flash messages | |
248 | def render_flash_messages |
|
248 | def render_flash_messages | |
249 | s = '' |
|
249 | s = '' | |
250 | flash.each do |k,v| |
|
250 | flash.each do |k,v| | |
251 | s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") |
|
251 | s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}") | |
252 | end |
|
252 | end | |
253 | s.html_safe |
|
253 | s.html_safe | |
254 | end |
|
254 | end | |
255 |
|
255 | |||
256 | # Renders tabs and their content |
|
256 | # Renders tabs and their content | |
257 | def render_tabs(tabs) |
|
257 | def render_tabs(tabs) | |
258 | if tabs.any? |
|
258 | if tabs.any? | |
259 | render :partial => 'common/tabs', :locals => {:tabs => tabs} |
|
259 | render :partial => 'common/tabs', :locals => {:tabs => tabs} | |
260 | else |
|
260 | else | |
261 | content_tag 'p', l(:label_no_data), :class => "nodata" |
|
261 | content_tag 'p', l(:label_no_data), :class => "nodata" | |
262 | end |
|
262 | end | |
263 | end |
|
263 | end | |
264 |
|
264 | |||
265 | # Renders the project quick-jump box |
|
265 | # Renders the project quick-jump box | |
266 | def render_project_jump_box |
|
266 | def render_project_jump_box | |
267 | return unless User.current.logged? |
|
267 | return unless User.current.logged? | |
268 | projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq |
|
268 | projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq | |
269 | if projects.any? |
|
269 | if projects.any? | |
270 | options = |
|
270 | options = | |
271 | ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" + |
|
271 | ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" + | |
272 | '<option value="" disabled="disabled">---</option>').html_safe |
|
272 | '<option value="" disabled="disabled">---</option>').html_safe | |
273 |
|
273 | |||
274 | options << project_tree_options_for_select(projects, :selected => @project) do |p| |
|
274 | options << project_tree_options_for_select(projects, :selected => @project) do |p| | |
275 | { :value => project_path(:id => p, :jump => current_menu_item) } |
|
275 | { :value => project_path(:id => p, :jump => current_menu_item) } | |
276 | end |
|
276 | end | |
277 |
|
277 | |||
278 | select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') |
|
278 | select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') | |
279 | end |
|
279 | end | |
280 | end |
|
280 | end | |
281 |
|
281 | |||
282 | def project_tree_options_for_select(projects, options = {}) |
|
282 | def project_tree_options_for_select(projects, options = {}) | |
283 | s = '' |
|
283 | s = '' | |
284 | project_tree(projects) do |project, level| |
|
284 | project_tree(projects) do |project, level| | |
285 | name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe |
|
285 | name_prefix = (level > 0 ? ' ' * 2 * level + '» ' : '').html_safe | |
286 | tag_options = {:value => project.id} |
|
286 | tag_options = {:value => project.id} | |
287 | if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) |
|
287 | if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) | |
288 | tag_options[:selected] = 'selected' |
|
288 | tag_options[:selected] = 'selected' | |
289 | else |
|
289 | else | |
290 | tag_options[:selected] = nil |
|
290 | tag_options[:selected] = nil | |
291 | end |
|
291 | end | |
292 | tag_options.merge!(yield(project)) if block_given? |
|
292 | tag_options.merge!(yield(project)) if block_given? | |
293 | s << content_tag('option', name_prefix + h(project), tag_options) |
|
293 | s << content_tag('option', name_prefix + h(project), tag_options) | |
294 | end |
|
294 | end | |
295 | s.html_safe |
|
295 | s.html_safe | |
296 | end |
|
296 | end | |
297 |
|
297 | |||
298 | # Yields the given block for each project with its level in the tree |
|
298 | # Yields the given block for each project with its level in the tree | |
299 | # |
|
299 | # | |
300 | # Wrapper for Project#project_tree |
|
300 | # Wrapper for Project#project_tree | |
301 | def project_tree(projects, &block) |
|
301 | def project_tree(projects, &block) | |
302 | Project.project_tree(projects, &block) |
|
302 | Project.project_tree(projects, &block) | |
303 | end |
|
303 | end | |
304 |
|
304 | |||
305 | def principals_check_box_tags(name, principals) |
|
305 | def principals_check_box_tags(name, principals) | |
306 | s = '' |
|
306 | s = '' | |
307 | principals.sort.each do |principal| |
|
307 | principals.sort.each do |principal| | |
308 | s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" |
|
308 | s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n" | |
309 | end |
|
309 | end | |
310 | s.html_safe |
|
310 | s.html_safe | |
311 | end |
|
311 | end | |
312 |
|
312 | |||
313 | # Returns a string for users/groups option tags |
|
313 | # Returns a string for users/groups option tags | |
314 | def principals_options_for_select(collection, selected=nil) |
|
314 | def principals_options_for_select(collection, selected=nil) | |
315 | s = '' |
|
315 | s = '' | |
316 | if collection.include?(User.current) |
|
316 | if collection.include?(User.current) | |
317 | s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) |
|
317 | s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id) | |
318 | end |
|
318 | end | |
319 | groups = '' |
|
319 | groups = '' | |
320 | collection.sort.each do |element| |
|
320 | collection.sort.each do |element| | |
321 | selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) |
|
321 | selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) | |
322 | (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) |
|
322 | (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>) | |
323 | end |
|
323 | end | |
324 | unless groups.empty? |
|
324 | unless groups.empty? | |
325 | s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) |
|
325 | s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>) | |
326 | end |
|
326 | end | |
327 | s.html_safe |
|
327 | s.html_safe | |
328 | end |
|
328 | end | |
329 |
|
329 | |||
330 | # Truncates and returns the string as a single line |
|
330 | # Truncates and returns the string as a single line | |
331 | def truncate_single_line(string, *args) |
|
331 | def truncate_single_line(string, *args) | |
332 | truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
|
332 | truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') | |
333 | end |
|
333 | end | |
334 |
|
334 | |||
335 | # Truncates at line break after 250 characters or options[:length] |
|
335 | # Truncates at line break after 250 characters or options[:length] | |
336 | def truncate_lines(string, options={}) |
|
336 | def truncate_lines(string, options={}) | |
337 | length = options[:length] || 250 |
|
337 | length = options[:length] || 250 | |
338 | if string.to_s =~ /\A(.{#{length}}.*?)$/m |
|
338 | if string.to_s =~ /\A(.{#{length}}.*?)$/m | |
339 | "#{$1}..." |
|
339 | "#{$1}..." | |
340 | else |
|
340 | else | |
341 | string |
|
341 | string | |
342 | end |
|
342 | end | |
343 | end |
|
343 | end | |
344 |
|
344 | |||
345 | def anchor(text) |
|
345 | def anchor(text) | |
346 | text.to_s.gsub(' ', '_') |
|
346 | text.to_s.gsub(' ', '_') | |
347 | end |
|
347 | end | |
348 |
|
348 | |||
349 | def html_hours(text) |
|
349 | def html_hours(text) | |
350 | text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe |
|
350 | text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe | |
351 | end |
|
351 | end | |
352 |
|
352 | |||
353 | def authoring(created, author, options={}) |
|
353 | def authoring(created, author, options={}) | |
354 | l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe |
|
354 | l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe | |
355 | end |
|
355 | end | |
356 |
|
356 | |||
357 | def time_tag(time) |
|
357 | def time_tag(time) | |
358 | text = distance_of_time_in_words(Time.now, time) |
|
358 | text = distance_of_time_in_words(Time.now, time) | |
359 | if @project |
|
359 | if @project | |
360 | link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) |
|
360 | link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time)) | |
361 | else |
|
361 | else | |
362 | content_tag('acronym', text, :title => format_time(time)) |
|
362 | content_tag('acronym', text, :title => format_time(time)) | |
363 | end |
|
363 | end | |
364 | end |
|
364 | end | |
365 |
|
365 | |||
366 | def syntax_highlight_lines(name, content) |
|
366 | def syntax_highlight_lines(name, content) | |
367 | lines = [] |
|
367 | lines = [] | |
368 | syntax_highlight(name, content).each_line { |line| lines << line } |
|
368 | syntax_highlight(name, content).each_line { |line| lines << line } | |
369 | lines |
|
369 | lines | |
370 | end |
|
370 | end | |
371 |
|
371 | |||
372 | def syntax_highlight(name, content) |
|
372 | def syntax_highlight(name, content) | |
373 | Redmine::SyntaxHighlighting.highlight_by_filename(content, name) |
|
373 | Redmine::SyntaxHighlighting.highlight_by_filename(content, name) | |
374 | end |
|
374 | end | |
375 |
|
375 | |||
376 | def to_path_param(path) |
|
376 | def to_path_param(path) | |
377 | str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") |
|
377 | str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/") | |
378 | str.blank? ? nil : str |
|
378 | str.blank? ? nil : str | |
379 | end |
|
379 | end | |
380 |
|
380 | |||
381 | def pagination_links_full(paginator, count=nil, options={}) |
|
381 | def pagination_links_full(paginator, count=nil, options={}) | |
382 | page_param = options.delete(:page_param) || :page |
|
382 | page_param = options.delete(:page_param) || :page | |
383 | per_page_links = options.delete(:per_page_links) |
|
383 | per_page_links = options.delete(:per_page_links) | |
384 | url_param = params.dup |
|
384 | url_param = params.dup | |
385 |
|
385 | |||
386 | html = '' |
|
386 | html = '' | |
387 | if paginator.current.previous |
|
387 | if paginator.current.previous | |
388 | # \xc2\xab(utf-8) = « |
|
388 | # \xc2\xab(utf-8) = « | |
389 | html << link_to_content_update( |
|
389 | html << link_to_content_update( | |
390 | "\xc2\xab " + l(:label_previous), |
|
390 | "\xc2\xab " + l(:label_previous), | |
391 | url_param.merge(page_param => paginator.current.previous)) + ' ' |
|
391 | url_param.merge(page_param => paginator.current.previous)) + ' ' | |
392 | end |
|
392 | end | |
393 |
|
393 | |||
394 | html << (pagination_links_each(paginator, options) do |n| |
|
394 | html << (pagination_links_each(paginator, options) do |n| | |
395 | link_to_content_update(n.to_s, url_param.merge(page_param => n)) |
|
395 | link_to_content_update(n.to_s, url_param.merge(page_param => n)) | |
396 | end || '') |
|
396 | end || '') | |
397 |
|
397 | |||
398 | if paginator.current.next |
|
398 | if paginator.current.next | |
399 | # \xc2\xbb(utf-8) = » |
|
399 | # \xc2\xbb(utf-8) = » | |
400 | html << ' ' + link_to_content_update( |
|
400 | html << ' ' + link_to_content_update( | |
401 | (l(:label_next) + " \xc2\xbb"), |
|
401 | (l(:label_next) + " \xc2\xbb"), | |
402 | url_param.merge(page_param => paginator.current.next)) |
|
402 | url_param.merge(page_param => paginator.current.next)) | |
403 | end |
|
403 | end | |
404 |
|
404 | |||
405 | unless count.nil? |
|
405 | unless count.nil? | |
406 | html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" |
|
406 | html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})" | |
407 | if per_page_links != false && links = per_page_links(paginator.items_per_page, count) |
|
407 | if per_page_links != false && links = per_page_links(paginator.items_per_page, count) | |
408 | html << " | #{links}" |
|
408 | html << " | #{links}" | |
409 | end |
|
409 | end | |
410 | end |
|
410 | end | |
411 |
|
411 | |||
412 | html.html_safe |
|
412 | html.html_safe | |
413 | end |
|
413 | end | |
414 |
|
414 | |||
415 | def per_page_links(selected=nil, item_count=nil) |
|
415 | def per_page_links(selected=nil, item_count=nil) | |
416 | values = Setting.per_page_options_array |
|
416 | values = Setting.per_page_options_array | |
417 | if item_count && values.any? |
|
417 | if item_count && values.any? | |
418 | if item_count > values.first |
|
418 | if item_count > values.first | |
419 | max = values.detect {|value| value >= item_count} || item_count |
|
419 | max = values.detect {|value| value >= item_count} || item_count | |
420 | else |
|
420 | else | |
421 | max = item_count |
|
421 | max = item_count | |
422 | end |
|
422 | end | |
423 | values = values.select {|value| value <= max || value == selected} |
|
423 | values = values.select {|value| value <= max || value == selected} | |
424 | end |
|
424 | end | |
425 | if values.empty? || (values.size == 1 && values.first == selected) |
|
425 | if values.empty? || (values.size == 1 && values.first == selected) | |
426 | return nil |
|
426 | return nil | |
427 | end |
|
427 | end | |
428 | links = values.collect do |n| |
|
428 | links = values.collect do |n| | |
429 | n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) |
|
429 | n == selected ? n : link_to_content_update(n, params.merge(:per_page => n)) | |
430 | end |
|
430 | end | |
431 | l(:label_display_per_page, links.join(', ')) |
|
431 | l(:label_display_per_page, links.join(', ')) | |
432 | end |
|
432 | end | |
433 |
|
433 | |||
434 | def reorder_links(name, url, method = :post) |
|
434 | def reorder_links(name, url, method = :post) | |
435 | link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), |
|
435 | link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), | |
436 | url.merge({"#{name}[move_to]" => 'highest'}), |
|
436 | url.merge({"#{name}[move_to]" => 'highest'}), | |
437 | :method => method, :title => l(:label_sort_highest)) + |
|
437 | :method => method, :title => l(:label_sort_highest)) + | |
438 | link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), |
|
438 | link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), | |
439 | url.merge({"#{name}[move_to]" => 'higher'}), |
|
439 | url.merge({"#{name}[move_to]" => 'higher'}), | |
440 | :method => method, :title => l(:label_sort_higher)) + |
|
440 | :method => method, :title => l(:label_sort_higher)) + | |
441 | link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), |
|
441 | link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), | |
442 | url.merge({"#{name}[move_to]" => 'lower'}), |
|
442 | url.merge({"#{name}[move_to]" => 'lower'}), | |
443 | :method => method, :title => l(:label_sort_lower)) + |
|
443 | :method => method, :title => l(:label_sort_lower)) + | |
444 | link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), |
|
444 | link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), | |
445 | url.merge({"#{name}[move_to]" => 'lowest'}), |
|
445 | url.merge({"#{name}[move_to]" => 'lowest'}), | |
446 | :method => method, :title => l(:label_sort_lowest)) |
|
446 | :method => method, :title => l(:label_sort_lowest)) | |
447 | end |
|
447 | end | |
448 |
|
448 | |||
449 | def breadcrumb(*args) |
|
449 | def breadcrumb(*args) | |
450 | elements = args.flatten |
|
450 | elements = args.flatten | |
451 | elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil |
|
451 | elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil | |
452 | end |
|
452 | end | |
453 |
|
453 | |||
454 | def other_formats_links(&block) |
|
454 | def other_formats_links(&block) | |
455 | concat('<p class="other-formats">'.html_safe + l(:label_export_to)) |
|
455 | concat('<p class="other-formats">'.html_safe + l(:label_export_to)) | |
456 | yield Redmine::Views::OtherFormatsBuilder.new(self) |
|
456 | yield Redmine::Views::OtherFormatsBuilder.new(self) | |
457 | concat('</p>'.html_safe) |
|
457 | concat('</p>'.html_safe) | |
458 | end |
|
458 | end | |
459 |
|
459 | |||
460 | def page_header_title |
|
460 | def page_header_title | |
461 | if @project.nil? || @project.new_record? |
|
461 | if @project.nil? || @project.new_record? | |
462 | h(Setting.app_title) |
|
462 | h(Setting.app_title) | |
463 | else |
|
463 | else | |
464 | b = [] |
|
464 | b = [] | |
465 | ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
|
465 | ancestors = (@project.root? ? [] : @project.ancestors.visible.all) | |
466 | if ancestors.any? |
|
466 | if ancestors.any? | |
467 | root = ancestors.shift |
|
467 | root = ancestors.shift | |
468 | b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') |
|
468 | b << link_to_project(root, {:jump => current_menu_item}, :class => 'root') | |
469 | if ancestors.size > 2 |
|
469 | if ancestors.size > 2 | |
470 | b << "\xe2\x80\xa6" |
|
470 | b << "\xe2\x80\xa6" | |
471 | ancestors = ancestors[-2, 2] |
|
471 | ancestors = ancestors[-2, 2] | |
472 | end |
|
472 | end | |
473 | b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } |
|
473 | b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') } | |
474 | end |
|
474 | end | |
475 | b << h(@project) |
|
475 | b << h(@project) | |
476 | b.join(" \xc2\xbb ").html_safe |
|
476 | b.join(" \xc2\xbb ").html_safe | |
477 | end |
|
477 | end | |
478 | end |
|
478 | end | |
479 |
|
479 | |||
480 | def html_title(*args) |
|
480 | def html_title(*args) | |
481 | if args.empty? |
|
481 | if args.empty? | |
482 | title = @html_title || [] |
|
482 | title = @html_title || [] | |
483 | title << @project.name if @project |
|
483 | title << @project.name if @project | |
484 | title << Setting.app_title unless Setting.app_title == title.last |
|
484 | title << Setting.app_title unless Setting.app_title == title.last | |
485 | title.select {|t| !t.blank? }.join(' - ') |
|
485 | title.select {|t| !t.blank? }.join(' - ') | |
486 | else |
|
486 | else | |
487 | @html_title ||= [] |
|
487 | @html_title ||= [] | |
488 | @html_title += args |
|
488 | @html_title += args | |
489 | end |
|
489 | end | |
490 | end |
|
490 | end | |
491 |
|
491 | |||
492 | # Returns the theme, controller name, and action as css classes for the |
|
492 | # Returns the theme, controller name, and action as css classes for the | |
493 | # HTML body. |
|
493 | # HTML body. | |
494 | def body_css_classes |
|
494 | def body_css_classes | |
495 | css = [] |
|
495 | css = [] | |
496 | if theme = Redmine::Themes.theme(Setting.ui_theme) |
|
496 | if theme = Redmine::Themes.theme(Setting.ui_theme) | |
497 | css << 'theme-' + theme.name |
|
497 | css << 'theme-' + theme.name | |
498 | end |
|
498 | end | |
499 |
|
499 | |||
500 | css << 'controller-' + controller_name |
|
500 | css << 'controller-' + controller_name | |
501 | css << 'action-' + action_name |
|
501 | css << 'action-' + action_name | |
502 | css.join(' ') |
|
502 | css.join(' ') | |
503 | end |
|
503 | end | |
504 |
|
504 | |||
505 | def accesskey(s) |
|
505 | def accesskey(s) | |
506 | Redmine::AccessKeys.key_for s |
|
506 | Redmine::AccessKeys.key_for s | |
507 | end |
|
507 | end | |
508 |
|
508 | |||
509 | # Formats text according to system settings. |
|
509 | # Formats text according to system settings. | |
510 | # 2 ways to call this method: |
|
510 | # 2 ways to call this method: | |
511 | # * with a String: textilizable(text, options) |
|
511 | # * with a String: textilizable(text, options) | |
512 | # * with an object and one of its attribute: textilizable(issue, :description, options) |
|
512 | # * with an object and one of its attribute: textilizable(issue, :description, options) | |
513 | def textilizable(*args) |
|
513 | def textilizable(*args) | |
514 | options = args.last.is_a?(Hash) ? args.pop : {} |
|
514 | options = args.last.is_a?(Hash) ? args.pop : {} | |
515 | case args.size |
|
515 | case args.size | |
516 | when 1 |
|
516 | when 1 | |
517 | obj = options[:object] |
|
517 | obj = options[:object] | |
518 | text = args.shift |
|
518 | text = args.shift | |
519 | when 2 |
|
519 | when 2 | |
520 | obj = args.shift |
|
520 | obj = args.shift | |
521 | attr = args.shift |
|
521 | attr = args.shift | |
522 | text = obj.send(attr).to_s |
|
522 | text = obj.send(attr).to_s | |
523 | else |
|
523 | else | |
524 | raise ArgumentError, 'invalid arguments to textilizable' |
|
524 | raise ArgumentError, 'invalid arguments to textilizable' | |
525 | end |
|
525 | end | |
526 | return '' if text.blank? |
|
526 | return '' if text.blank? | |
527 | project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
|
527 | project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) | |
528 | only_path = options.delete(:only_path) == false ? false : true |
|
528 | only_path = options.delete(:only_path) == false ? false : true | |
529 |
|
529 | |||
530 | text = text.dup |
|
530 | text = text.dup | |
531 | macros = catch_macros(text) |
|
531 | macros = catch_macros(text) | |
532 | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) |
|
532 | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) | |
533 |
|
533 | |||
534 | @parsed_headings = [] |
|
534 | @parsed_headings = [] | |
535 | @heading_anchors = {} |
|
535 | @heading_anchors = {} | |
536 | @current_section = 0 if options[:edit_section_links] |
|
536 | @current_section = 0 if options[:edit_section_links] | |
537 |
|
537 | |||
538 | parse_sections(text, project, obj, attr, only_path, options) |
|
538 | parse_sections(text, project, obj, attr, only_path, options) | |
539 | text = parse_non_pre_blocks(text, obj, macros) do |text| |
|
539 | text = parse_non_pre_blocks(text, obj, macros) do |text| | |
540 | [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| |
|
540 | [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name| | |
541 | send method_name, text, project, obj, attr, only_path, options |
|
541 | send method_name, text, project, obj, attr, only_path, options | |
542 | end |
|
542 | end | |
543 | end |
|
543 | end | |
544 | parse_headings(text, project, obj, attr, only_path, options) |
|
544 | parse_headings(text, project, obj, attr, only_path, options) | |
545 |
|
545 | |||
546 | if @parsed_headings.any? |
|
546 | if @parsed_headings.any? | |
547 | replace_toc(text, @parsed_headings) |
|
547 | replace_toc(text, @parsed_headings) | |
548 | end |
|
548 | end | |
549 |
|
549 | |||
550 | text.html_safe |
|
550 | text.html_safe | |
551 | end |
|
551 | end | |
552 |
|
552 | |||
553 | def parse_non_pre_blocks(text, obj, macros) |
|
553 | def parse_non_pre_blocks(text, obj, macros) | |
554 | s = StringScanner.new(text) |
|
554 | s = StringScanner.new(text) | |
555 | tags = [] |
|
555 | tags = [] | |
556 | parsed = '' |
|
556 | parsed = '' | |
557 | while !s.eos? |
|
557 | while !s.eos? | |
558 | s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) |
|
558 | s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) | |
559 | text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
|
559 | text, full_tag, closing, tag = s[1], s[2], s[3], s[4] | |
560 | if tags.empty? |
|
560 | if tags.empty? | |
561 | yield text |
|
561 | yield text | |
562 | inject_macros(text, obj, macros) if macros.any? |
|
562 | inject_macros(text, obj, macros) if macros.any? | |
563 | else |
|
563 | else | |
564 | inject_macros(text, obj, macros, false) if macros.any? |
|
564 | inject_macros(text, obj, macros, false) if macros.any? | |
565 | end |
|
565 | end | |
566 | parsed << text |
|
566 | parsed << text | |
567 | if tag |
|
567 | if tag | |
568 | if closing |
|
568 | if closing | |
569 | if tags.last == tag.downcase |
|
569 | if tags.last == tag.downcase | |
570 | tags.pop |
|
570 | tags.pop | |
571 | end |
|
571 | end | |
572 | else |
|
572 | else | |
573 | tags << tag.downcase |
|
573 | tags << tag.downcase | |
574 | end |
|
574 | end | |
575 | parsed << full_tag |
|
575 | parsed << full_tag | |
576 | end |
|
576 | end | |
577 | end |
|
577 | end | |
578 | # Close any non closing tags |
|
578 | # Close any non closing tags | |
579 | while tag = tags.pop |
|
579 | while tag = tags.pop | |
580 | parsed << "</#{tag}>" |
|
580 | parsed << "</#{tag}>" | |
581 | end |
|
581 | end | |
582 | parsed |
|
582 | parsed | |
583 | end |
|
583 | end | |
584 |
|
584 | |||
585 | def parse_inline_attachments(text, project, obj, attr, only_path, options) |
|
585 | def parse_inline_attachments(text, project, obj, attr, only_path, options) | |
586 | # when using an image link, try to use an attachment, if possible |
|
586 | # when using an image link, try to use an attachment, if possible | |
587 | if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
|
587 | if options[:attachments] || (obj && obj.respond_to?(:attachments)) | |
588 | attachments = options[:attachments] || obj.attachments |
|
588 | attachments = options[:attachments] || obj.attachments | |
589 | text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
|
589 | text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| | |
590 | filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
|
590 | filename, ext, alt, alttext = $1.downcase, $2, $3, $4 | |
591 | # search for the picture in attachments |
|
591 | # search for the picture in attachments | |
592 | if found = Attachment.latest_attach(attachments, filename) |
|
592 | if found = Attachment.latest_attach(attachments, filename) | |
593 | image_url = url_for :only_path => only_path, :controller => 'attachments', |
|
593 | image_url = url_for :only_path => only_path, :controller => 'attachments', | |
594 | :action => 'download', :id => found |
|
594 | :action => 'download', :id => found | |
595 | desc = found.description.to_s.gsub('"', '') |
|
595 | desc = found.description.to_s.gsub('"', '') | |
596 | if !desc.blank? && alttext.blank? |
|
596 | if !desc.blank? && alttext.blank? | |
597 | alt = " title=\"#{desc}\" alt=\"#{desc}\"" |
|
597 | alt = " title=\"#{desc}\" alt=\"#{desc}\"" | |
598 | end |
|
598 | end | |
599 | "src=\"#{image_url}\"#{alt}" |
|
599 | "src=\"#{image_url}\"#{alt}" | |
600 | else |
|
600 | else | |
601 | m |
|
601 | m | |
602 | end |
|
602 | end | |
603 | end |
|
603 | end | |
604 | end |
|
604 | end | |
605 | end |
|
605 | end | |
606 |
|
606 | |||
607 | # Wiki links |
|
607 | # Wiki links | |
608 | # |
|
608 | # | |
609 | # Examples: |
|
609 | # Examples: | |
610 | # [[mypage]] |
|
610 | # [[mypage]] | |
611 | # [[mypage|mytext]] |
|
611 | # [[mypage|mytext]] | |
612 | # wiki links can refer other project wikis, using project name or identifier: |
|
612 | # wiki links can refer other project wikis, using project name or identifier: | |
613 | # [[project:]] -> wiki starting page |
|
613 | # [[project:]] -> wiki starting page | |
614 | # [[project:|mytext]] |
|
614 | # [[project:|mytext]] | |
615 | # [[project:mypage]] |
|
615 | # [[project:mypage]] | |
616 | # [[project:mypage|mytext]] |
|
616 | # [[project:mypage|mytext]] | |
617 | def parse_wiki_links(text, project, obj, attr, only_path, options) |
|
617 | def parse_wiki_links(text, project, obj, attr, only_path, options) | |
618 | text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| |
|
618 | text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| | |
619 | link_project = project |
|
619 | link_project = project | |
620 | esc, all, page, title = $1, $2, $3, $5 |
|
620 | esc, all, page, title = $1, $2, $3, $5 | |
621 | if esc.nil? |
|
621 | if esc.nil? | |
622 | if page =~ /^([^\:]+)\:(.*)$/ |
|
622 | if page =~ /^([^\:]+)\:(.*)$/ | |
623 | link_project = Project.find_by_identifier($1) || Project.find_by_name($1) |
|
623 | link_project = Project.find_by_identifier($1) || Project.find_by_name($1) | |
624 | page = $2 |
|
624 | page = $2 | |
625 | title ||= $1 if page.blank? |
|
625 | title ||= $1 if page.blank? | |
626 | end |
|
626 | end | |
627 |
|
627 | |||
628 | if link_project && link_project.wiki |
|
628 | if link_project && link_project.wiki | |
629 | # extract anchor |
|
629 | # extract anchor | |
630 | anchor = nil |
|
630 | anchor = nil | |
631 | if page =~ /^(.+?)\#(.+)$/ |
|
631 | if page =~ /^(.+?)\#(.+)$/ | |
632 | page, anchor = $1, $2 |
|
632 | page, anchor = $1, $2 | |
633 | end |
|
633 | end | |
634 | anchor = sanitize_anchor_name(anchor) if anchor.present? |
|
634 | anchor = sanitize_anchor_name(anchor) if anchor.present? | |
635 | # check if page exists |
|
635 | # check if page exists | |
636 | wiki_page = link_project.wiki.find_page(page) |
|
636 | wiki_page = link_project.wiki.find_page(page) | |
637 | url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page |
|
637 | url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page | |
638 | "##{anchor}" |
|
638 | "##{anchor}" | |
639 | else |
|
639 | else | |
640 | case options[:wiki_links] |
|
640 | case options[:wiki_links] | |
641 | when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') |
|
641 | when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '') | |
642 | when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export |
|
642 | when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export | |
643 | else |
|
643 | else | |
644 | wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
|
644 | wiki_page_id = page.present? ? Wiki.titleize(page) : nil | |
645 | parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil |
|
645 | parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil | |
646 | url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, |
|
646 | url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, | |
647 | :id => wiki_page_id, :anchor => anchor, :parent => parent) |
|
647 | :id => wiki_page_id, :anchor => anchor, :parent => parent) | |
648 | end |
|
648 | end | |
649 | end |
|
649 | end | |
650 | link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) |
|
650 | link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) | |
651 | else |
|
651 | else | |
652 | # project or wiki doesn't exist |
|
652 | # project or wiki doesn't exist | |
653 | all |
|
653 | all | |
654 | end |
|
654 | end | |
655 | else |
|
655 | else | |
656 | all |
|
656 | all | |
657 | end |
|
657 | end | |
658 | end |
|
658 | end | |
659 | end |
|
659 | end | |
660 |
|
660 | |||
661 | # Redmine links |
|
661 | # Redmine links | |
662 | # |
|
662 | # | |
663 | # Examples: |
|
663 | # Examples: | |
664 | # Issues: |
|
664 | # Issues: | |
665 | # #52 -> Link to issue #52 |
|
665 | # #52 -> Link to issue #52 | |
666 | # Changesets: |
|
666 | # Changesets: | |
667 | # r52 -> Link to revision 52 |
|
667 | # r52 -> Link to revision 52 | |
668 | # commit:a85130f -> Link to scmid starting with a85130f |
|
668 | # commit:a85130f -> Link to scmid starting with a85130f | |
669 | # Documents: |
|
669 | # Documents: | |
670 | # document#17 -> Link to document with id 17 |
|
670 | # document#17 -> Link to document with id 17 | |
671 | # document:Greetings -> Link to the document with title "Greetings" |
|
671 | # document:Greetings -> Link to the document with title "Greetings" | |
672 | # document:"Some document" -> Link to the document with title "Some document" |
|
672 | # document:"Some document" -> Link to the document with title "Some document" | |
673 | # Versions: |
|
673 | # Versions: | |
674 | # version#3 -> Link to version with id 3 |
|
674 | # version#3 -> Link to version with id 3 | |
675 | # version:1.0.0 -> Link to version named "1.0.0" |
|
675 | # version:1.0.0 -> Link to version named "1.0.0" | |
676 | # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" |
|
676 | # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" | |
677 | # Attachments: |
|
677 | # Attachments: | |
678 | # attachment:file.zip -> Link to the attachment of the current object named file.zip |
|
678 | # attachment:file.zip -> Link to the attachment of the current object named file.zip | |
679 | # Source files: |
|
679 | # Source files: | |
680 | # source:some/file -> Link to the file located at /some/file in the project's repository |
|
680 | # source:some/file -> Link to the file located at /some/file in the project's repository | |
681 | # source:some/file@52 -> Link to the file's revision 52 |
|
681 | # source:some/file@52 -> Link to the file's revision 52 | |
682 | # source:some/file#L120 -> Link to line 120 of the file |
|
682 | # source:some/file#L120 -> Link to line 120 of the file | |
683 | # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 |
|
683 | # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 | |
684 | # export:some/file -> Force the download of the file |
|
684 | # export:some/file -> Force the download of the file | |
685 | # Forum messages: |
|
685 | # Forum messages: | |
686 | # message#1218 -> Link to message with id 1218 |
|
686 | # message#1218 -> Link to message with id 1218 | |
687 | # |
|
687 | # | |
688 | # Links can refer other objects from other projects, using project identifier: |
|
688 | # Links can refer other objects from other projects, using project identifier: | |
689 | # identifier:r52 |
|
689 | # identifier:r52 | |
690 | # identifier:document:"Some document" |
|
690 | # identifier:document:"Some document" | |
691 | # identifier:version:1.0.0 |
|
691 | # identifier:version:1.0.0 | |
692 | # identifier:source:some/file |
|
692 | # identifier:source:some/file | |
693 | def parse_redmine_links(text, project, obj, attr, only_path, options) |
|
693 | def parse_redmine_links(text, project, obj, attr, only_path, options) | |
694 | text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| |
|
694 | text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m| | |
695 | leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 |
|
695 | leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17 | |
696 | link = nil |
|
696 | link = nil | |
697 | if project_identifier |
|
697 | if project_identifier | |
698 | project = Project.visible.find_by_identifier(project_identifier) |
|
698 | project = Project.visible.find_by_identifier(project_identifier) | |
699 | end |
|
699 | end | |
700 | if esc.nil? |
|
700 | if esc.nil? | |
701 | if prefix.nil? && sep == 'r' |
|
701 | if prefix.nil? && sep == 'r' | |
702 | if project |
|
702 | if project | |
703 | repository = nil |
|
703 | repository = nil | |
704 | if repo_identifier |
|
704 | if repo_identifier | |
705 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
|
705 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} | |
706 | else |
|
706 | else | |
707 | repository = project.repository |
|
707 | repository = project.repository | |
708 | end |
|
708 | end | |
709 | # project.changesets.visible raises an SQL error because of a double join on repositories |
|
709 | # project.changesets.visible raises an SQL error because of a double join on repositories | |
710 | if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) |
|
710 | if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier)) | |
711 | link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, |
|
711 | link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision}, | |
712 | :class => 'changeset', |
|
712 | :class => 'changeset', | |
713 | :title => truncate_single_line(changeset.comments, :length => 100)) |
|
713 | :title => truncate_single_line(changeset.comments, :length => 100)) | |
714 | end |
|
714 | end | |
715 | end |
|
715 | end | |
716 | elsif sep == '#' |
|
716 | elsif sep == '#' | |
717 | oid = identifier.to_i |
|
717 | oid = identifier.to_i | |
718 | case prefix |
|
718 | case prefix | |
719 | when nil |
|
719 | when nil | |
720 | if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) |
|
720 | if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status) | |
721 | anchor = comment_id ? "note-#{comment_id}" : nil |
|
721 | anchor = comment_id ? "note-#{comment_id}" : nil | |
722 | link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, |
|
722 | link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor}, | |
723 | :class => issue.css_classes, |
|
723 | :class => issue.css_classes, | |
724 | :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
|
724 | :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") | |
725 | end |
|
725 | end | |
726 | when 'document' |
|
726 | when 'document' | |
727 | if document = Document.visible.find_by_id(oid) |
|
727 | if document = Document.visible.find_by_id(oid) | |
728 | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, |
|
728 | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, | |
729 | :class => 'document' |
|
729 | :class => 'document' | |
730 | end |
|
730 | end | |
731 | when 'version' |
|
731 | when 'version' | |
732 | if version = Version.visible.find_by_id(oid) |
|
732 | if version = Version.visible.find_by_id(oid) | |
733 | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, |
|
733 | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, | |
734 | :class => 'version' |
|
734 | :class => 'version' | |
735 | end |
|
735 | end | |
736 | when 'message' |
|
736 | when 'message' | |
737 | if message = Message.visible.find_by_id(oid, :include => :parent) |
|
737 | if message = Message.visible.find_by_id(oid, :include => :parent) | |
738 | link = link_to_message(message, {:only_path => only_path}, :class => 'message') |
|
738 | link = link_to_message(message, {:only_path => only_path}, :class => 'message') | |
739 | end |
|
739 | end | |
740 | when 'forum' |
|
740 | when 'forum' | |
741 | if board = Board.visible.find_by_id(oid) |
|
741 | if board = Board.visible.find_by_id(oid) | |
742 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, |
|
742 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, | |
743 | :class => 'board' |
|
743 | :class => 'board' | |
744 | end |
|
744 | end | |
745 | when 'news' |
|
745 | when 'news' | |
746 | if news = News.visible.find_by_id(oid) |
|
746 | if news = News.visible.find_by_id(oid) | |
747 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, |
|
747 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | |
748 | :class => 'news' |
|
748 | :class => 'news' | |
749 | end |
|
749 | end | |
750 | when 'project' |
|
750 | when 'project' | |
751 | if p = Project.visible.find_by_id(oid) |
|
751 | if p = Project.visible.find_by_id(oid) | |
752 | link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
|
752 | link = link_to_project(p, {:only_path => only_path}, :class => 'project') | |
753 | end |
|
753 | end | |
754 | end |
|
754 | end | |
755 | elsif sep == ':' |
|
755 | elsif sep == ':' | |
756 | # removes the double quotes if any |
|
756 | # removes the double quotes if any | |
757 | name = identifier.gsub(%r{^"(.*)"$}, "\\1") |
|
757 | name = identifier.gsub(%r{^"(.*)"$}, "\\1") | |
758 | case prefix |
|
758 | case prefix | |
759 | when 'document' |
|
759 | when 'document' | |
760 | if project && document = project.documents.visible.find_by_title(name) |
|
760 | if project && document = project.documents.visible.find_by_title(name) | |
761 | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, |
|
761 | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document}, | |
762 | :class => 'document' |
|
762 | :class => 'document' | |
763 | end |
|
763 | end | |
764 | when 'version' |
|
764 | when 'version' | |
765 | if project && version = project.versions.visible.find_by_name(name) |
|
765 | if project && version = project.versions.visible.find_by_name(name) | |
766 | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, |
|
766 | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, | |
767 | :class => 'version' |
|
767 | :class => 'version' | |
768 | end |
|
768 | end | |
769 | when 'forum' |
|
769 | when 'forum' | |
770 | if project && board = project.boards.visible.find_by_name(name) |
|
770 | if project && board = project.boards.visible.find_by_name(name) | |
771 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, |
|
771 | link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project}, | |
772 | :class => 'board' |
|
772 | :class => 'board' | |
773 | end |
|
773 | end | |
774 | when 'news' |
|
774 | when 'news' | |
775 | if project && news = project.news.visible.find_by_title(name) |
|
775 | if project && news = project.news.visible.find_by_title(name) | |
776 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, |
|
776 | link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news}, | |
777 | :class => 'news' |
|
777 | :class => 'news' | |
778 | end |
|
778 | end | |
779 | when 'commit', 'source', 'export' |
|
779 | when 'commit', 'source', 'export' | |
780 | if project |
|
780 | if project | |
781 | repository = nil |
|
781 | repository = nil | |
782 | if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$} |
|
782 | if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$} | |
783 | repo_prefix, repo_identifier, name = $1, $2, $3 |
|
783 | repo_prefix, repo_identifier, name = $1, $2, $3 | |
784 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} |
|
784 | repository = project.repositories.detect {|repo| repo.identifier == repo_identifier} | |
785 | else |
|
785 | else | |
786 | repository = project.repository |
|
786 | repository = project.repository | |
787 | end |
|
787 | end | |
788 | if prefix == 'commit' |
|
788 | if prefix == 'commit' | |
789 | if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) |
|
789 | if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"])) | |
790 | link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, |
|
790 | link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier}, | |
791 | :class => 'changeset', |
|
791 | :class => 'changeset', | |
792 | :title => truncate_single_line(h(changeset.comments), :length => 100) |
|
792 | :title => truncate_single_line(h(changeset.comments), :length => 100) | |
793 | end |
|
793 | end | |
794 | else |
|
794 | else | |
795 | if repository && User.current.allowed_to?(:browse_repository, project) |
|
795 | if repository && User.current.allowed_to?(:browse_repository, project) | |
796 | name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} |
|
796 | name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$} | |
797 | path, rev, anchor = $1, $3, $5 |
|
797 | path, rev, anchor = $1, $3, $5 | |
798 | link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param, |
|
798 | link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param, | |
799 | :path => to_path_param(path), |
|
799 | :path => to_path_param(path), | |
800 | :rev => rev, |
|
800 | :rev => rev, | |
801 | :anchor => anchor, |
|
801 | :anchor => anchor, | |
802 | :format => (prefix == 'export' ? 'raw' : nil)}, |
|
802 | :format => (prefix == 'export' ? 'raw' : nil)}, | |
803 | :class => (prefix == 'export' ? 'source download' : 'source') |
|
803 | :class => (prefix == 'export' ? 'source download' : 'source') | |
804 | end |
|
804 | end | |
805 | end |
|
805 | end | |
806 | repo_prefix = nil |
|
806 | repo_prefix = nil | |
807 | end |
|
807 | end | |
808 | when 'attachment' |
|
808 | when 'attachment' | |
809 | attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
|
809 | attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) | |
810 | if attachments && attachment = attachments.detect {|a| a.filename == name } |
|
810 | if attachments && attachment = attachments.detect {|a| a.filename == name } | |
811 | link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, |
|
811 | link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, | |
812 | :class => 'attachment' |
|
812 | :class => 'attachment' | |
813 | end |
|
813 | end | |
814 | when 'project' |
|
814 | when 'project' | |
815 | if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) |
|
815 | if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) | |
816 | link = link_to_project(p, {:only_path => only_path}, :class => 'project') |
|
816 | link = link_to_project(p, {:only_path => only_path}, :class => 'project') | |
817 | end |
|
817 | end | |
818 | end |
|
818 | end | |
819 | end |
|
819 | end | |
820 | end |
|
820 | end | |
821 | (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) |
|
821 | (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}")) | |
822 | end |
|
822 | end | |
823 | end |
|
823 | end | |
824 |
|
824 | |||
825 | HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) |
|
825 | HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE) | |
826 |
|
826 | |||
827 | def parse_sections(text, project, obj, attr, only_path, options) |
|
827 | def parse_sections(text, project, obj, attr, only_path, options) | |
828 | return unless options[:edit_section_links] |
|
828 | return unless options[:edit_section_links] | |
829 | text.gsub!(HEADING_RE) do |
|
829 | text.gsub!(HEADING_RE) do | |
830 | heading = $1 |
|
830 | heading = $1 | |
831 | @current_section += 1 |
|
831 | @current_section += 1 | |
832 | if @current_section > 1 |
|
832 | if @current_section > 1 | |
833 | content_tag('div', |
|
833 | content_tag('div', | |
834 | link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), |
|
834 | link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)), | |
835 | :class => 'contextual', |
|
835 | :class => 'contextual', | |
836 | :title => l(:button_edit_section)) + heading.html_safe |
|
836 | :title => l(:button_edit_section)) + heading.html_safe | |
837 | else |
|
837 | else | |
838 | heading |
|
838 | heading | |
839 | end |
|
839 | end | |
840 | end |
|
840 | end | |
841 | end |
|
841 | end | |
842 |
|
842 | |||
843 | # Headings and TOC |
|
843 | # Headings and TOC | |
844 | # Adds ids and links to headings unless options[:headings] is set to false |
|
844 | # Adds ids and links to headings unless options[:headings] is set to false | |
845 | def parse_headings(text, project, obj, attr, only_path, options) |
|
845 | def parse_headings(text, project, obj, attr, only_path, options) | |
846 | return if options[:headings] == false |
|
846 | return if options[:headings] == false | |
847 |
|
847 | |||
848 | text.gsub!(HEADING_RE) do |
|
848 | text.gsub!(HEADING_RE) do | |
849 | level, attrs, content = $2.to_i, $3, $4 |
|
849 | level, attrs, content = $2.to_i, $3, $4 | |
850 | item = strip_tags(content).strip |
|
850 | item = strip_tags(content).strip | |
851 | anchor = sanitize_anchor_name(item) |
|
851 | anchor = sanitize_anchor_name(item) | |
852 | # used for single-file wiki export |
|
852 | # used for single-file wiki export | |
853 | anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) |
|
853 | anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) | |
854 | @heading_anchors[anchor] ||= 0 |
|
854 | @heading_anchors[anchor] ||= 0 | |
855 | idx = (@heading_anchors[anchor] += 1) |
|
855 | idx = (@heading_anchors[anchor] += 1) | |
856 | if idx > 1 |
|
856 | if idx > 1 | |
857 | anchor = "#{anchor}-#{idx}" |
|
857 | anchor = "#{anchor}-#{idx}" | |
858 | end |
|
858 | end | |
859 | @parsed_headings << [level, anchor, item] |
|
859 | @parsed_headings << [level, anchor, item] | |
860 | "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" |
|
860 | "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>" | |
861 | end |
|
861 | end | |
862 | end |
|
862 | end | |
863 |
|
863 | |||
864 | MACROS_RE = /( |
|
864 | MACROS_RE = /( | |
865 | (!)? # escaping |
|
865 | (!)? # escaping | |
866 | ( |
|
866 | ( | |
867 | \{\{ # opening tag |
|
867 | \{\{ # opening tag | |
868 | ([\w]+) # macro name |
|
868 | ([\w]+) # macro name | |
869 | (\(([^\n\r]*?)\))? # optional arguments |
|
869 | (\(([^\n\r]*?)\))? # optional arguments | |
870 | ([\n\r].*?[\n\r])? # optional block of text |
|
870 | ([\n\r].*?[\n\r])? # optional block of text | |
871 | \}\} # closing tag |
|
871 | \}\} # closing tag | |
872 | ) |
|
872 | ) | |
873 | )/mx unless const_defined?(:MACROS_RE) |
|
873 | )/mx unless const_defined?(:MACROS_RE) | |
874 |
|
874 | |||
875 | MACRO_SUB_RE = /( |
|
875 | MACRO_SUB_RE = /( | |
876 | \{\{ |
|
876 | \{\{ | |
877 | macro\((\d+)\) |
|
877 | macro\((\d+)\) | |
878 | \}\} |
|
878 | \}\} | |
879 | )/x unless const_defined?(:MACRO_SUB_RE) |
|
879 | )/x unless const_defined?(:MACRO_SUB_RE) | |
880 |
|
880 | |||
881 | # Extracts macros from text |
|
881 | # Extracts macros from text | |
882 | def catch_macros(text) |
|
882 | def catch_macros(text) | |
883 | macros = {} |
|
883 | macros = {} | |
884 | text.gsub!(MACROS_RE) do |
|
884 | text.gsub!(MACROS_RE) do | |
885 | all, macro = $1, $4.downcase |
|
885 | all, macro = $1, $4.downcase | |
886 | if macro_exists?(macro) || all =~ MACRO_SUB_RE |
|
886 | if macro_exists?(macro) || all =~ MACRO_SUB_RE | |
887 | index = macros.size |
|
887 | index = macros.size | |
888 | macros[index] = all |
|
888 | macros[index] = all | |
889 | "{{macro(#{index})}}" |
|
889 | "{{macro(#{index})}}" | |
890 | else |
|
890 | else | |
891 | all |
|
891 | all | |
892 | end |
|
892 | end | |
893 | end |
|
893 | end | |
894 | macros |
|
894 | macros | |
895 | end |
|
895 | end | |
896 |
|
896 | |||
897 | # Executes and replaces macros in text |
|
897 | # Executes and replaces macros in text | |
898 | def inject_macros(text, obj, macros, execute=true) |
|
898 | def inject_macros(text, obj, macros, execute=true) | |
899 | text.gsub!(MACRO_SUB_RE) do |
|
899 | text.gsub!(MACRO_SUB_RE) do | |
900 | all, index = $1, $2.to_i |
|
900 | all, index = $1, $2.to_i | |
901 | orig = macros.delete(index) |
|
901 | orig = macros.delete(index) | |
902 | if execute && orig && orig =~ MACROS_RE |
|
902 | if execute && orig && orig =~ MACROS_RE | |
903 | esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) |
|
903 | esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip) | |
904 | if esc.nil? |
|
904 | if esc.nil? | |
905 | h(exec_macro(macro, obj, args, block) || all) |
|
905 | h(exec_macro(macro, obj, args, block) || all) | |
906 | else |
|
906 | else | |
907 | h(all) |
|
907 | h(all) | |
908 | end |
|
908 | end | |
909 | elsif orig |
|
909 | elsif orig | |
910 | h(orig) |
|
910 | h(orig) | |
911 | else |
|
911 | else | |
912 | h(all) |
|
912 | h(all) | |
913 | end |
|
913 | end | |
914 | end |
|
914 | end | |
915 | end |
|
915 | end | |
916 |
|
916 | |||
917 | TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
|
917 | TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) | |
918 |
|
918 | |||
919 | # Renders the TOC with given headings |
|
919 | # Renders the TOC with given headings | |
920 | def replace_toc(text, headings) |
|
920 | def replace_toc(text, headings) | |
921 | text.gsub!(TOC_RE) do |
|
921 | text.gsub!(TOC_RE) do | |
922 | # Keep only the 4 first levels |
|
922 | # Keep only the 4 first levels | |
923 | headings = headings.select{|level, anchor, item| level <= 4} |
|
923 | headings = headings.select{|level, anchor, item| level <= 4} | |
924 | if headings.empty? |
|
924 | if headings.empty? | |
925 | '' |
|
925 | '' | |
926 | else |
|
926 | else | |
927 | div_class = 'toc' |
|
927 | div_class = 'toc' | |
928 | div_class << ' right' if $1 == '>' |
|
928 | div_class << ' right' if $1 == '>' | |
929 | div_class << ' left' if $1 == '<' |
|
929 | div_class << ' left' if $1 == '<' | |
930 | out = "<ul class=\"#{div_class}\"><li>" |
|
930 | out = "<ul class=\"#{div_class}\"><li>" | |
931 | root = headings.map(&:first).min |
|
931 | root = headings.map(&:first).min | |
932 | current = root |
|
932 | current = root | |
933 | started = false |
|
933 | started = false | |
934 | headings.each do |level, anchor, item| |
|
934 | headings.each do |level, anchor, item| | |
935 | if level > current |
|
935 | if level > current | |
936 | out << '<ul><li>' * (level - current) |
|
936 | out << '<ul><li>' * (level - current) | |
937 | elsif level < current |
|
937 | elsif level < current | |
938 | out << "</li></ul>\n" * (current - level) + "</li><li>" |
|
938 | out << "</li></ul>\n" * (current - level) + "</li><li>" | |
939 | elsif started |
|
939 | elsif started | |
940 | out << '</li><li>' |
|
940 | out << '</li><li>' | |
941 | end |
|
941 | end | |
942 | out << "<a href=\"##{anchor}\">#{item}</a>" |
|
942 | out << "<a href=\"##{anchor}\">#{item}</a>" | |
943 | current = level |
|
943 | current = level | |
944 | started = true |
|
944 | started = true | |
945 | end |
|
945 | end | |
946 | out << '</li></ul>' * (current - root) |
|
946 | out << '</li></ul>' * (current - root) | |
947 | out << '</li></ul>' |
|
947 | out << '</li></ul>' | |
948 | end |
|
948 | end | |
949 | end |
|
949 | end | |
950 | end |
|
950 | end | |
951 |
|
951 | |||
952 | # Same as Rails' simple_format helper without using paragraphs |
|
952 | # Same as Rails' simple_format helper without using paragraphs | |
953 | def simple_format_without_paragraph(text) |
|
953 | def simple_format_without_paragraph(text) | |
954 | text.to_s. |
|
954 | text.to_s. | |
955 | gsub(/\r\n?/, "\n"). # \r\n and \r -> \n |
|
955 | gsub(/\r\n?/, "\n"). # \r\n and \r -> \n | |
956 | gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br |
|
956 | gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br | |
957 | gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br |
|
957 | gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br | |
958 | html_safe |
|
958 | html_safe | |
959 | end |
|
959 | end | |
960 |
|
960 | |||
961 | def lang_options_for_select(blank=true) |
|
961 | def lang_options_for_select(blank=true) | |
962 | (blank ? [["(auto)", ""]] : []) + |
|
962 | (blank ? [["(auto)", ""]] : []) + | |
963 | valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } |
|
963 | valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last } | |
964 | end |
|
964 | end | |
965 |
|
965 | |||
966 | def label_tag_for(name, option_tags = nil, options = {}) |
|
966 | def label_tag_for(name, option_tags = nil, options = {}) | |
967 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
|
967 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") | |
968 | content_tag("label", label_text) |
|
968 | content_tag("label", label_text) | |
969 | end |
|
969 | end | |
970 |
|
970 | |||
971 | def labelled_form_for(*args, &proc) |
|
971 | def labelled_form_for(*args, &proc) | |
972 | args << {} unless args.last.is_a?(Hash) |
|
972 | args << {} unless args.last.is_a?(Hash) | |
973 | options = args.last |
|
973 | options = args.last | |
974 | if args.first.is_a?(Symbol) |
|
974 | if args.first.is_a?(Symbol) | |
975 | options.merge!(:as => args.shift) |
|
975 | options.merge!(:as => args.shift) | |
976 | end |
|
976 | end | |
977 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) |
|
977 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
978 | form_for(*args, &proc) |
|
978 | form_for(*args, &proc) | |
979 | end |
|
979 | end | |
980 |
|
980 | |||
981 | def labelled_fields_for(*args, &proc) |
|
981 | def labelled_fields_for(*args, &proc) | |
982 | args << {} unless args.last.is_a?(Hash) |
|
982 | args << {} unless args.last.is_a?(Hash) | |
983 | options = args.last |
|
983 | options = args.last | |
984 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) |
|
984 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder}) | |
985 | fields_for(*args, &proc) |
|
985 | fields_for(*args, &proc) | |
986 | end |
|
986 | end | |
987 |
|
987 | |||
988 | def labelled_remote_form_for(*args, &proc) |
|
988 | def labelled_remote_form_for(*args, &proc) | |
989 | ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." |
|
989 | ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2." | |
990 | args << {} unless args.last.is_a?(Hash) |
|
990 | args << {} unless args.last.is_a?(Hash) | |
991 | options = args.last |
|
991 | options = args.last | |
992 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) |
|
992 | options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true}) | |
993 | form_for(*args, &proc) |
|
993 | form_for(*args, &proc) | |
994 | end |
|
994 | end | |
995 |
|
995 | |||
996 | def error_messages_for(*objects) |
|
996 | def error_messages_for(*objects) | |
997 | html = "" |
|
997 | html = "" | |
998 | objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact |
|
998 | objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact | |
999 | errors = objects.map {|o| o.errors.full_messages}.flatten |
|
999 | errors = objects.map {|o| o.errors.full_messages}.flatten | |
1000 | if errors.any? |
|
1000 | if errors.any? | |
1001 | html << "<div id='errorExplanation'><ul>\n" |
|
1001 | html << "<div id='errorExplanation'><ul>\n" | |
1002 | errors.each do |error| |
|
1002 | errors.each do |error| | |
1003 | html << "<li>#{h error}</li>\n" |
|
1003 | html << "<li>#{h error}</li>\n" | |
1004 | end |
|
1004 | end | |
1005 | html << "</ul></div>\n" |
|
1005 | html << "</ul></div>\n" | |
1006 | end |
|
1006 | end | |
1007 | html.html_safe |
|
1007 | html.html_safe | |
1008 | end |
|
1008 | end | |
1009 |
|
1009 | |||
1010 | def delete_link(url, options={}) |
|
1010 | def delete_link(url, options={}) | |
1011 | options = { |
|
1011 | options = { | |
1012 | :method => :delete, |
|
1012 | :method => :delete, | |
1013 | :data => {:confirm => l(:text_are_you_sure)}, |
|
1013 | :data => {:confirm => l(:text_are_you_sure)}, | |
1014 | :class => 'icon icon-del' |
|
1014 | :class => 'icon icon-del' | |
1015 | }.merge(options) |
|
1015 | }.merge(options) | |
1016 |
|
1016 | |||
1017 | link_to l(:button_delete), url, options |
|
1017 | link_to l(:button_delete), url, options | |
1018 | end |
|
1018 | end | |
1019 |
|
1019 | |||
1020 | def preview_link(url, form, target='preview', options={}) |
|
1020 | def preview_link(url, form, target='preview', options={}) | |
1021 | content_tag 'a', l(:label_preview), { |
|
1021 | content_tag 'a', l(:label_preview), { | |
1022 | :href => "#", |
|
1022 | :href => "#", | |
1023 | :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, |
|
1023 | :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|, | |
1024 | :accesskey => accesskey(:preview) |
|
1024 | :accesskey => accesskey(:preview) | |
1025 | }.merge(options) |
|
1025 | }.merge(options) | |
1026 | end |
|
1026 | end | |
1027 |
|
1027 | |||
1028 | def link_to_function(name, function, html_options={}) |
|
1028 | def link_to_function(name, function, html_options={}) | |
1029 | content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) |
|
1029 | content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options)) | |
1030 | end |
|
1030 | end | |
1031 |
|
1031 | |||
|
1032 | # Helper to render JSON in views | |||
|
1033 | def raw_json(arg) | |||
|
1034 | arg.to_json.to_s.gsub('/', '\/').html_safe | |||
|
1035 | end | |||
|
1036 | ||||
1032 | def back_url |
|
1037 | def back_url | |
1033 | url = params[:back_url] |
|
1038 | url = params[:back_url] | |
1034 | if url.nil? && referer = request.env['HTTP_REFERER'] |
|
1039 | if url.nil? && referer = request.env['HTTP_REFERER'] | |
1035 | url = CGI.unescape(referer.to_s) |
|
1040 | url = CGI.unescape(referer.to_s) | |
1036 | end |
|
1041 | end | |
1037 | url |
|
1042 | url | |
1038 | end |
|
1043 | end | |
1039 |
|
1044 | |||
1040 | def back_url_hidden_field_tag |
|
1045 | def back_url_hidden_field_tag | |
1041 | url = back_url |
|
1046 | url = back_url | |
1042 | hidden_field_tag('back_url', url, :id => nil) unless url.blank? |
|
1047 | hidden_field_tag('back_url', url, :id => nil) unless url.blank? | |
1043 | end |
|
1048 | end | |
1044 |
|
1049 | |||
1045 | def check_all_links(form_name) |
|
1050 | def check_all_links(form_name) | |
1046 | link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
|
1051 | link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + | |
1047 | " | ".html_safe + |
|
1052 | " | ".html_safe + | |
1048 | link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") |
|
1053 | link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") | |
1049 | end |
|
1054 | end | |
1050 |
|
1055 | |||
1051 | def progress_bar(pcts, options={}) |
|
1056 | def progress_bar(pcts, options={}) | |
1052 | pcts = [pcts, pcts] unless pcts.is_a?(Array) |
|
1057 | pcts = [pcts, pcts] unless pcts.is_a?(Array) | |
1053 | pcts = pcts.collect(&:round) |
|
1058 | pcts = pcts.collect(&:round) | |
1054 | pcts[1] = pcts[1] - pcts[0] |
|
1059 | pcts[1] = pcts[1] - pcts[0] | |
1055 | pcts << (100 - pcts[1] - pcts[0]) |
|
1060 | pcts << (100 - pcts[1] - pcts[0]) | |
1056 | width = options[:width] || '100px;' |
|
1061 | width = options[:width] || '100px;' | |
1057 | legend = options[:legend] || '' |
|
1062 | legend = options[:legend] || '' | |
1058 | content_tag('table', |
|
1063 | content_tag('table', | |
1059 | content_tag('tr', |
|
1064 | content_tag('tr', | |
1060 | (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + |
|
1065 | (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) + | |
1061 | (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + |
|
1066 | (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) + | |
1062 | (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) |
|
1067 | (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe) | |
1063 | ), :class => 'progress', :style => "width: #{width};").html_safe + |
|
1068 | ), :class => 'progress', :style => "width: #{width};").html_safe + | |
1064 | content_tag('p', legend, :class => 'pourcent').html_safe |
|
1069 | content_tag('p', legend, :class => 'pourcent').html_safe | |
1065 | end |
|
1070 | end | |
1066 |
|
1071 | |||
1067 | def checked_image(checked=true) |
|
1072 | def checked_image(checked=true) | |
1068 | if checked |
|
1073 | if checked | |
1069 | image_tag 'toggle_check.png' |
|
1074 | image_tag 'toggle_check.png' | |
1070 | end |
|
1075 | end | |
1071 | end |
|
1076 | end | |
1072 |
|
1077 | |||
1073 | def context_menu(url) |
|
1078 | def context_menu(url) | |
1074 | unless @context_menu_included |
|
1079 | unless @context_menu_included | |
1075 | content_for :header_tags do |
|
1080 | content_for :header_tags do | |
1076 | javascript_include_tag('context_menu') + |
|
1081 | javascript_include_tag('context_menu') + | |
1077 | stylesheet_link_tag('context_menu') |
|
1082 | stylesheet_link_tag('context_menu') | |
1078 | end |
|
1083 | end | |
1079 | if l(:direction) == 'rtl' |
|
1084 | if l(:direction) == 'rtl' | |
1080 | content_for :header_tags do |
|
1085 | content_for :header_tags do | |
1081 | stylesheet_link_tag('context_menu_rtl') |
|
1086 | stylesheet_link_tag('context_menu_rtl') | |
1082 | end |
|
1087 | end | |
1083 | end |
|
1088 | end | |
1084 | @context_menu_included = true |
|
1089 | @context_menu_included = true | |
1085 | end |
|
1090 | end | |
1086 | javascript_tag "contextMenuInit('#{ url_for(url) }')" |
|
1091 | javascript_tag "contextMenuInit('#{ url_for(url) }')" | |
1087 | end |
|
1092 | end | |
1088 |
|
1093 | |||
1089 | def calendar_for(field_id) |
|
1094 | def calendar_for(field_id) | |
1090 | include_calendar_headers_tags |
|
1095 | include_calendar_headers_tags | |
1091 | javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") |
|
1096 | javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });") | |
1092 | end |
|
1097 | end | |
1093 |
|
1098 | |||
1094 | def include_calendar_headers_tags |
|
1099 | def include_calendar_headers_tags | |
1095 | unless @calendar_headers_tags_included |
|
1100 | unless @calendar_headers_tags_included | |
1096 | @calendar_headers_tags_included = true |
|
1101 | @calendar_headers_tags_included = true | |
1097 | content_for :header_tags do |
|
1102 | content_for :header_tags do | |
1098 | start_of_week = Setting.start_of_week |
|
1103 | start_of_week = Setting.start_of_week | |
1099 | start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? |
|
1104 | start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank? | |
1100 | # Redmine uses 1..7 (monday..sunday) in settings and locales |
|
1105 | # Redmine uses 1..7 (monday..sunday) in settings and locales | |
1101 | # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 |
|
1106 | # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0 | |
1102 | start_of_week = start_of_week.to_i % 7 |
|
1107 | start_of_week = start_of_week.to_i % 7 | |
1103 |
|
1108 | |||
1104 | tags = javascript_tag( |
|
1109 | tags = javascript_tag( | |
1105 | "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + |
|
1110 | "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " + | |
1106 | "showOn: 'button', buttonImageOnly: true, buttonImage: '" + |
|
1111 | "showOn: 'button', buttonImageOnly: true, buttonImage: '" + | |
1107 | path_to_image('/images/calendar.png') + |
|
1112 | path_to_image('/images/calendar.png') + | |
1108 | "', showButtonPanel: true};") |
|
1113 | "', showButtonPanel: true};") | |
1109 | jquery_locale = l('jquery.locale', :default => current_language.to_s) |
|
1114 | jquery_locale = l('jquery.locale', :default => current_language.to_s) | |
1110 | unless jquery_locale == 'en' |
|
1115 | unless jquery_locale == 'en' | |
1111 | tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") |
|
1116 | tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js") | |
1112 | end |
|
1117 | end | |
1113 | tags |
|
1118 | tags | |
1114 | end |
|
1119 | end | |
1115 | end |
|
1120 | end | |
1116 | end |
|
1121 | end | |
1117 |
|
1122 | |||
1118 | # Overrides Rails' stylesheet_link_tag with themes and plugins support. |
|
1123 | # Overrides Rails' stylesheet_link_tag with themes and plugins support. | |
1119 | # Examples: |
|
1124 | # Examples: | |
1120 | # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults |
|
1125 | # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults | |
1121 | # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets |
|
1126 | # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets | |
1122 | # |
|
1127 | # | |
1123 | def stylesheet_link_tag(*sources) |
|
1128 | def stylesheet_link_tag(*sources) | |
1124 | options = sources.last.is_a?(Hash) ? sources.pop : {} |
|
1129 | options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1125 | plugin = options.delete(:plugin) |
|
1130 | plugin = options.delete(:plugin) | |
1126 | sources = sources.map do |source| |
|
1131 | sources = sources.map do |source| | |
1127 | if plugin |
|
1132 | if plugin | |
1128 | "/plugin_assets/#{plugin}/stylesheets/#{source}" |
|
1133 | "/plugin_assets/#{plugin}/stylesheets/#{source}" | |
1129 | elsif current_theme && current_theme.stylesheets.include?(source) |
|
1134 | elsif current_theme && current_theme.stylesheets.include?(source) | |
1130 | current_theme.stylesheet_path(source) |
|
1135 | current_theme.stylesheet_path(source) | |
1131 | else |
|
1136 | else | |
1132 | source |
|
1137 | source | |
1133 | end |
|
1138 | end | |
1134 | end |
|
1139 | end | |
1135 | super sources, options |
|
1140 | super sources, options | |
1136 | end |
|
1141 | end | |
1137 |
|
1142 | |||
1138 | # Overrides Rails' image_tag with themes and plugins support. |
|
1143 | # Overrides Rails' image_tag with themes and plugins support. | |
1139 | # Examples: |
|
1144 | # Examples: | |
1140 | # image_tag('image.png') # => picks image.png from the current theme or defaults |
|
1145 | # image_tag('image.png') # => picks image.png from the current theme or defaults | |
1141 | # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets |
|
1146 | # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets | |
1142 | # |
|
1147 | # | |
1143 | def image_tag(source, options={}) |
|
1148 | def image_tag(source, options={}) | |
1144 | if plugin = options.delete(:plugin) |
|
1149 | if plugin = options.delete(:plugin) | |
1145 | source = "/plugin_assets/#{plugin}/images/#{source}" |
|
1150 | source = "/plugin_assets/#{plugin}/images/#{source}" | |
1146 | elsif current_theme && current_theme.images.include?(source) |
|
1151 | elsif current_theme && current_theme.images.include?(source) | |
1147 | source = current_theme.image_path(source) |
|
1152 | source = current_theme.image_path(source) | |
1148 | end |
|
1153 | end | |
1149 | super source, options |
|
1154 | super source, options | |
1150 | end |
|
1155 | end | |
1151 |
|
1156 | |||
1152 | # Overrides Rails' javascript_include_tag with plugins support |
|
1157 | # Overrides Rails' javascript_include_tag with plugins support | |
1153 | # Examples: |
|
1158 | # Examples: | |
1154 | # javascript_include_tag('scripts') # => picks scripts.js from defaults |
|
1159 | # javascript_include_tag('scripts') # => picks scripts.js from defaults | |
1155 | # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets |
|
1160 | # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets | |
1156 | # |
|
1161 | # | |
1157 | def javascript_include_tag(*sources) |
|
1162 | def javascript_include_tag(*sources) | |
1158 | options = sources.last.is_a?(Hash) ? sources.pop : {} |
|
1163 | options = sources.last.is_a?(Hash) ? sources.pop : {} | |
1159 | if plugin = options.delete(:plugin) |
|
1164 | if plugin = options.delete(:plugin) | |
1160 | sources = sources.map do |source| |
|
1165 | sources = sources.map do |source| | |
1161 | if plugin |
|
1166 | if plugin | |
1162 | "/plugin_assets/#{plugin}/javascripts/#{source}" |
|
1167 | "/plugin_assets/#{plugin}/javascripts/#{source}" | |
1163 | else |
|
1168 | else | |
1164 | source |
|
1169 | source | |
1165 | end |
|
1170 | end | |
1166 | end |
|
1171 | end | |
1167 | end |
|
1172 | end | |
1168 | super sources, options |
|
1173 | super sources, options | |
1169 | end |
|
1174 | end | |
1170 |
|
1175 | |||
1171 | def content_for(name, content = nil, &block) |
|
1176 | def content_for(name, content = nil, &block) | |
1172 | @has_content ||= {} |
|
1177 | @has_content ||= {} | |
1173 | @has_content[name] = true |
|
1178 | @has_content[name] = true | |
1174 | super(name, content, &block) |
|
1179 | super(name, content, &block) | |
1175 | end |
|
1180 | end | |
1176 |
|
1181 | |||
1177 | def has_content?(name) |
|
1182 | def has_content?(name) | |
1178 | (@has_content && @has_content[name]) || false |
|
1183 | (@has_content && @has_content[name]) || false | |
1179 | end |
|
1184 | end | |
1180 |
|
1185 | |||
1181 | def sidebar_content? |
|
1186 | def sidebar_content? | |
1182 | has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? |
|
1187 | has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present? | |
1183 | end |
|
1188 | end | |
1184 |
|
1189 | |||
1185 | def view_layouts_base_sidebar_hook_response |
|
1190 | def view_layouts_base_sidebar_hook_response | |
1186 | @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) |
|
1191 | @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar) | |
1187 | end |
|
1192 | end | |
1188 |
|
1193 | |||
1189 | def email_delivery_enabled? |
|
1194 | def email_delivery_enabled? | |
1190 | !!ActionMailer::Base.perform_deliveries |
|
1195 | !!ActionMailer::Base.perform_deliveries | |
1191 | end |
|
1196 | end | |
1192 |
|
1197 | |||
1193 | # Returns the avatar image tag for the given +user+ if avatars are enabled |
|
1198 | # Returns the avatar image tag for the given +user+ if avatars are enabled | |
1194 | # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
|
1199 | # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') | |
1195 | def avatar(user, options = { }) |
|
1200 | def avatar(user, options = { }) | |
1196 | if Setting.gravatar_enabled? |
|
1201 | if Setting.gravatar_enabled? | |
1197 | options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) |
|
1202 | options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default}) | |
1198 | email = nil |
|
1203 | email = nil | |
1199 | if user.respond_to?(:mail) |
|
1204 | if user.respond_to?(:mail) | |
1200 | email = user.mail |
|
1205 | email = user.mail | |
1201 | elsif user.to_s =~ %r{<(.+?)>} |
|
1206 | elsif user.to_s =~ %r{<(.+?)>} | |
1202 | email = $1 |
|
1207 | email = $1 | |
1203 | end |
|
1208 | end | |
1204 | return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil |
|
1209 | return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil | |
1205 | else |
|
1210 | else | |
1206 | '' |
|
1211 | '' | |
1207 | end |
|
1212 | end | |
1208 | end |
|
1213 | end | |
1209 |
|
1214 | |||
1210 | def sanitize_anchor_name(anchor) |
|
1215 | def sanitize_anchor_name(anchor) | |
1211 | if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' |
|
1216 | if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java' | |
1212 | anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
|
1217 | anchor.gsub(%r{[^\p{Word}\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1213 | else |
|
1218 | else | |
1214 | # TODO: remove when ruby1.8 is no longer supported |
|
1219 | # TODO: remove when ruby1.8 is no longer supported | |
1215 | anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
|
1220 | anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') | |
1216 | end |
|
1221 | end | |
1217 | end |
|
1222 | end | |
1218 |
|
1223 | |||
1219 | # Returns the javascript tags that are included in the html layout head |
|
1224 | # Returns the javascript tags that are included in the html layout head | |
1220 | def javascript_heads |
|
1225 | def javascript_heads | |
1221 | tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') |
|
1226 | tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.3', 'application') | |
1222 | unless User.current.pref.warn_on_leaving_unsaved == '0' |
|
1227 | unless User.current.pref.warn_on_leaving_unsaved == '0' | |
1223 | tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") |
|
1228 | tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") | |
1224 | end |
|
1229 | end | |
1225 | tags |
|
1230 | tags | |
1226 | end |
|
1231 | end | |
1227 |
|
1232 | |||
1228 | def favicon |
|
1233 | def favicon | |
1229 | "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe |
|
1234 | "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe | |
1230 | end |
|
1235 | end | |
1231 |
|
1236 | |||
1232 | def robot_exclusion_tag |
|
1237 | def robot_exclusion_tag | |
1233 | '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe |
|
1238 | '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe | |
1234 | end |
|
1239 | end | |
1235 |
|
1240 | |||
1236 | # Returns true if arg is expected in the API response |
|
1241 | # Returns true if arg is expected in the API response | |
1237 | def include_in_api_response?(arg) |
|
1242 | def include_in_api_response?(arg) | |
1238 | unless @included_in_api_response |
|
1243 | unless @included_in_api_response | |
1239 | param = params[:include] |
|
1244 | param = params[:include] | |
1240 | @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') |
|
1245 | @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') | |
1241 | @included_in_api_response.collect!(&:strip) |
|
1246 | @included_in_api_response.collect!(&:strip) | |
1242 | end |
|
1247 | end | |
1243 | @included_in_api_response.include?(arg.to_s) |
|
1248 | @included_in_api_response.include?(arg.to_s) | |
1244 | end |
|
1249 | end | |
1245 |
|
1250 | |||
1246 | # Returns options or nil if nometa param or X-Redmine-Nometa header |
|
1251 | # Returns options or nil if nometa param or X-Redmine-Nometa header | |
1247 | # was set in the request |
|
1252 | # was set in the request | |
1248 | def api_meta(options) |
|
1253 | def api_meta(options) | |
1249 | if params[:nometa].present? || request.headers['X-Redmine-Nometa'] |
|
1254 | if params[:nometa].present? || request.headers['X-Redmine-Nometa'] | |
1250 | # compatibility mode for activeresource clients that raise |
|
1255 | # compatibility mode for activeresource clients that raise | |
1251 | # an error when unserializing an array with attributes |
|
1256 | # an error when unserializing an array with attributes | |
1252 | nil |
|
1257 | nil | |
1253 | else |
|
1258 | else | |
1254 | options |
|
1259 | options | |
1255 | end |
|
1260 | end | |
1256 | end |
|
1261 | end | |
1257 |
|
1262 | |||
1258 | private |
|
1263 | private | |
1259 |
|
1264 | |||
1260 | def wiki_helper |
|
1265 | def wiki_helper | |
1261 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
|
1266 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) | |
1262 | extend helper |
|
1267 | extend helper | |
1263 | return self |
|
1268 | return self | |
1264 | end |
|
1269 | end | |
1265 |
|
1270 | |||
1266 | def link_to_content_update(text, url_params = {}, html_options = {}) |
|
1271 | def link_to_content_update(text, url_params = {}, html_options = {}) | |
1267 | link_to(text, url_params, html_options) |
|
1272 | link_to(text, url_params, html_options) | |
1268 | end |
|
1273 | end | |
1269 | end |
|
1274 | end |
@@ -1,27 +1,27 | |||||
1 | <%= javascript_tag do %> |
|
1 | <%= javascript_tag do %> | |
2 |
var operatorLabels = <%= raw Query.operators_labels |
|
2 | var operatorLabels = <%= raw_json Query.operators_labels %>; | |
3 |
var operatorByType = <%= raw Query.operators_by_filter_type |
|
3 | var operatorByType = <%= raw_json Query.operators_by_filter_type %>; | |
4 |
var availableFilters = <%= raw query.available_filters_as |
|
4 | var availableFilters = <%= raw_json query.available_filters_as_json %>; | |
5 |
var labelDayPlural = |
|
5 | var labelDayPlural = <%= raw_json l(:label_day_plural) %>; | |
6 | $(document).ready(function(){ |
|
6 | $(document).ready(function(){ | |
7 | initFilters(); |
|
7 | initFilters(); | |
8 | <% query.filters.each do |field, options| %> |
|
8 | <% query.filters.each do |field, options| %> | |
9 |
addFilter("<%= field %>", <%= raw query.operator_for(field) |
|
9 | addFilter("<%= field %>", <%= raw_json query.operator_for(field) %>, <%= raw_json query.values_for(field) %>); | |
10 | <% end %> |
|
10 | <% end %> | |
11 | }); |
|
11 | }); | |
12 | <% end %> |
|
12 | <% end %> | |
13 |
|
13 | |||
14 | <table style="width:100%"> |
|
14 | <table style="width:100%"> | |
15 | <tr> |
|
15 | <tr> | |
16 | <td> |
|
16 | <td> | |
17 | <table id="filters-table"> |
|
17 | <table id="filters-table"> | |
18 | </table> |
|
18 | </table> | |
19 | </td> |
|
19 | </td> | |
20 | <td class="add-filter"> |
|
20 | <td class="add-filter"> | |
21 | <%= label_tag('add_filter_select', l(:label_filter_add)) %> |
|
21 | <%= label_tag('add_filter_select', l(:label_filter_add)) %> | |
22 | <%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %> |
|
22 | <%= select_tag 'add_filter_select', filters_options_for_select(query), :name => nil %> | |
23 | </td> |
|
23 | </td> | |
24 | </tr> |
|
24 | </tr> | |
25 | </table> |
|
25 | </table> | |
26 | <%= hidden_field_tag 'f[]', '' %> |
|
26 | <%= hidden_field_tag 'f[]', '' %> | |
27 | <% include_calendar_headers_tags %> |
|
27 | <% include_calendar_headers_tags %> |
@@ -1,582 +1,582 | |||||
1 | /* Redmine - project management software |
|
1 | /* Redmine - project management software | |
2 | Copyright (C) 2006-2012 Jean-Philippe Lang */ |
|
2 | Copyright (C) 2006-2012 Jean-Philippe Lang */ | |
3 |
|
3 | |||
4 | function checkAll(id, checked) { |
|
4 | function checkAll(id, checked) { | |
5 | if (checked) { |
|
5 | if (checked) { | |
6 | $('#'+id).find('input[type=checkbox]').attr('checked', true); |
|
6 | $('#'+id).find('input[type=checkbox]').attr('checked', true); | |
7 | } else { |
|
7 | } else { | |
8 | $('#'+id).find('input[type=checkbox]').removeAttr('checked'); |
|
8 | $('#'+id).find('input[type=checkbox]').removeAttr('checked'); | |
9 | } |
|
9 | } | |
10 | } |
|
10 | } | |
11 |
|
11 | |||
12 | function toggleCheckboxesBySelector(selector) { |
|
12 | function toggleCheckboxesBySelector(selector) { | |
13 | var all_checked = true; |
|
13 | var all_checked = true; | |
14 | $(selector).each(function(index) { |
|
14 | $(selector).each(function(index) { | |
15 | if (!$(this).is(':checked')) { all_checked = false; } |
|
15 | if (!$(this).is(':checked')) { all_checked = false; } | |
16 | }); |
|
16 | }); | |
17 | $(selector).attr('checked', !all_checked) |
|
17 | $(selector).attr('checked', !all_checked) | |
18 | } |
|
18 | } | |
19 |
|
19 | |||
20 | function showAndScrollTo(id, focus) { |
|
20 | function showAndScrollTo(id, focus) { | |
21 | $('#'+id).show(); |
|
21 | $('#'+id).show(); | |
22 | if (focus!=null) { |
|
22 | if (focus!=null) { | |
23 | $('#'+focus).focus(); |
|
23 | $('#'+focus).focus(); | |
24 | } |
|
24 | } | |
25 | $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); |
|
25 | $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100); | |
26 | } |
|
26 | } | |
27 |
|
27 | |||
28 | function toggleRowGroup(el) { |
|
28 | function toggleRowGroup(el) { | |
29 | var tr = $(el).parents('tr').first(); |
|
29 | var tr = $(el).parents('tr').first(); | |
30 | var n = tr.next(); |
|
30 | var n = tr.next(); | |
31 | tr.toggleClass('open'); |
|
31 | tr.toggleClass('open'); | |
32 | while (n.length && !n.hasClass('group')) { |
|
32 | while (n.length && !n.hasClass('group')) { | |
33 | n.toggle(); |
|
33 | n.toggle(); | |
34 | n = n.next('tr'); |
|
34 | n = n.next('tr'); | |
35 | } |
|
35 | } | |
36 | } |
|
36 | } | |
37 |
|
37 | |||
38 | function collapseAllRowGroups(el) { |
|
38 | function collapseAllRowGroups(el) { | |
39 | var tbody = $(el).parents('tbody').first(); |
|
39 | var tbody = $(el).parents('tbody').first(); | |
40 | tbody.children('tr').each(function(index) { |
|
40 | tbody.children('tr').each(function(index) { | |
41 | if ($(this).hasClass('group')) { |
|
41 | if ($(this).hasClass('group')) { | |
42 | $(this).removeClass('open'); |
|
42 | $(this).removeClass('open'); | |
43 | } else { |
|
43 | } else { | |
44 | $(this).hide(); |
|
44 | $(this).hide(); | |
45 | } |
|
45 | } | |
46 | }); |
|
46 | }); | |
47 | } |
|
47 | } | |
48 |
|
48 | |||
49 | function expandAllRowGroups(el) { |
|
49 | function expandAllRowGroups(el) { | |
50 | var tbody = $(el).parents('tbody').first(); |
|
50 | var tbody = $(el).parents('tbody').first(); | |
51 | tbody.children('tr').each(function(index) { |
|
51 | tbody.children('tr').each(function(index) { | |
52 | if ($(this).hasClass('group')) { |
|
52 | if ($(this).hasClass('group')) { | |
53 | $(this).addClass('open'); |
|
53 | $(this).addClass('open'); | |
54 | } else { |
|
54 | } else { | |
55 | $(this).show(); |
|
55 | $(this).show(); | |
56 | } |
|
56 | } | |
57 | }); |
|
57 | }); | |
58 | } |
|
58 | } | |
59 |
|
59 | |||
60 | function toggleAllRowGroups(el) { |
|
60 | function toggleAllRowGroups(el) { | |
61 | var tr = $(el).parents('tr').first(); |
|
61 | var tr = $(el).parents('tr').first(); | |
62 | if (tr.hasClass('open')) { |
|
62 | if (tr.hasClass('open')) { | |
63 | collapseAllRowGroups(el); |
|
63 | collapseAllRowGroups(el); | |
64 | } else { |
|
64 | } else { | |
65 | expandAllRowGroups(el); |
|
65 | expandAllRowGroups(el); | |
66 | } |
|
66 | } | |
67 | } |
|
67 | } | |
68 |
|
68 | |||
69 | function toggleFieldset(el) { |
|
69 | function toggleFieldset(el) { | |
70 | var fieldset = $(el).parents('fieldset').first(); |
|
70 | var fieldset = $(el).parents('fieldset').first(); | |
71 | fieldset.toggleClass('collapsed'); |
|
71 | fieldset.toggleClass('collapsed'); | |
72 | fieldset.children('div').toggle(); |
|
72 | fieldset.children('div').toggle(); | |
73 | } |
|
73 | } | |
74 |
|
74 | |||
75 | function hideFieldset(el) { |
|
75 | function hideFieldset(el) { | |
76 | var fieldset = $(el).parents('fieldset').first(); |
|
76 | var fieldset = $(el).parents('fieldset').first(); | |
77 | fieldset.toggleClass('collapsed'); |
|
77 | fieldset.toggleClass('collapsed'); | |
78 | fieldset.children('div').hide(); |
|
78 | fieldset.children('div').hide(); | |
79 | } |
|
79 | } | |
80 |
|
80 | |||
81 | function initFilters(){ |
|
81 | function initFilters(){ | |
82 | $('#add_filter_select').change(function(){ |
|
82 | $('#add_filter_select').change(function(){ | |
83 | addFilter($(this).val(), '', []); |
|
83 | addFilter($(this).val(), '', []); | |
84 | }); |
|
84 | }); | |
85 | $('#filters-table td.field input[type=checkbox]').each(function(){ |
|
85 | $('#filters-table td.field input[type=checkbox]').each(function(){ | |
86 | toggleFilter($(this).val()); |
|
86 | toggleFilter($(this).val()); | |
87 | }); |
|
87 | }); | |
88 | $('#filters-table td.field input[type=checkbox]').live('click',function(){ |
|
88 | $('#filters-table td.field input[type=checkbox]').live('click',function(){ | |
89 | toggleFilter($(this).val()); |
|
89 | toggleFilter($(this).val()); | |
90 | }); |
|
90 | }); | |
91 | $('#filters-table .toggle-multiselect').live('click',function(){ |
|
91 | $('#filters-table .toggle-multiselect').live('click',function(){ | |
92 | toggleMultiSelect($(this).siblings('select')); |
|
92 | toggleMultiSelect($(this).siblings('select')); | |
93 | }); |
|
93 | }); | |
94 | $('#filters-table input[type=text]').live('keypress', function(e){ |
|
94 | $('#filters-table input[type=text]').live('keypress', function(e){ | |
95 | if (e.keyCode == 13) submit_query_form("query_form"); |
|
95 | if (e.keyCode == 13) submit_query_form("query_form"); | |
96 | }); |
|
96 | }); | |
97 | } |
|
97 | } | |
98 |
|
98 | |||
99 | function addFilter(field, operator, values) { |
|
99 | function addFilter(field, operator, values) { | |
100 | var fieldId = field.replace('.', '_'); |
|
100 | var fieldId = field.replace('.', '_'); | |
101 | var tr = $('#tr_'+fieldId); |
|
101 | var tr = $('#tr_'+fieldId); | |
102 | if (tr.length > 0) { |
|
102 | if (tr.length > 0) { | |
103 | tr.show(); |
|
103 | tr.show(); | |
104 | } else { |
|
104 | } else { | |
105 | buildFilterRow(field, operator, values); |
|
105 | buildFilterRow(field, operator, values); | |
106 | } |
|
106 | } | |
107 | $('#cb_'+fieldId).attr('checked', true); |
|
107 | $('#cb_'+fieldId).attr('checked', true); | |
108 | toggleFilter(field); |
|
108 | toggleFilter(field); | |
109 | $('#add_filter_select').val('').children('option').each(function(){ |
|
109 | $('#add_filter_select').val('').children('option').each(function(){ | |
110 | if ($(this).attr('value') == field) { |
|
110 | if ($(this).attr('value') == field) { | |
111 | $(this).attr('disabled', true); |
|
111 | $(this).attr('disabled', true); | |
112 | } |
|
112 | } | |
113 | }); |
|
113 | }); | |
114 | } |
|
114 | } | |
115 |
|
115 | |||
116 | function buildFilterRow(field, operator, values) { |
|
116 | function buildFilterRow(field, operator, values) { | |
117 | var fieldId = field.replace('.', '_'); |
|
117 | var fieldId = field.replace('.', '_'); | |
118 | var filterTable = $("#filters-table"); |
|
118 | var filterTable = $("#filters-table"); | |
119 | var filterOptions = availableFilters[field]; |
|
119 | var filterOptions = availableFilters[field]; | |
120 | var operators = operatorByType[filterOptions['type']]; |
|
120 | var operators = operatorByType[filterOptions['type']]; | |
121 | var filterValues = filterOptions['values']; |
|
121 | var filterValues = filterOptions['values']; | |
122 | var i, select; |
|
122 | var i, select; | |
123 |
|
123 | |||
124 | var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( |
|
124 | var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html( | |
125 | '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + |
|
125 | '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' + | |
126 | '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + |
|
126 | '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' + | |
127 | '<td class="values"></td>' |
|
127 | '<td class="values"></td>' | |
128 | ); |
|
128 | ); | |
129 | filterTable.append(tr); |
|
129 | filterTable.append(tr); | |
130 |
|
130 | |||
131 | select = tr.find('td.operator select'); |
|
131 | select = tr.find('td.operator select'); | |
132 | for (i=0;i<operators.length;i++){ |
|
132 | for (i=0;i<operators.length;i++){ | |
133 | var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]); |
|
133 | var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]); | |
134 | if (operators[i] == operator) {option.attr('selected', true)}; |
|
134 | if (operators[i] == operator) {option.attr('selected', true)}; | |
135 | select.append(option); |
|
135 | select.append(option); | |
136 | } |
|
136 | } | |
137 | select.change(function(){toggleOperator(field)}); |
|
137 | select.change(function(){toggleOperator(field)}); | |
138 |
|
138 | |||
139 | switch (filterOptions['type']){ |
|
139 | switch (filterOptions['type']){ | |
140 | case "list": |
|
140 | case "list": | |
141 | case "list_optional": |
|
141 | case "list_optional": | |
142 | case "list_status": |
|
142 | case "list_status": | |
143 | case "list_subprojects": |
|
143 | case "list_subprojects": | |
144 | tr.find('td.values').append( |
|
144 | tr.find('td.values').append( | |
145 | '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + |
|
145 | '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' + | |
146 | ' <span class="toggle-multiselect"> </span></span>' |
|
146 | ' <span class="toggle-multiselect"> </span></span>' | |
147 | ); |
|
147 | ); | |
148 | select = tr.find('td.values select'); |
|
148 | select = tr.find('td.values select'); | |
149 | if (values.length > 1) {select.attr('multiple', true)}; |
|
149 | if (values.length > 1) {select.attr('multiple', true)}; | |
150 | for (i=0;i<filterValues.length;i++){ |
|
150 | for (i=0;i<filterValues.length;i++){ | |
151 | var filterValue = filterValues[i]; |
|
151 | var filterValue = filterValues[i]; | |
152 | var option = $('<option>'); |
|
152 | var option = $('<option>'); | |
153 | if ($.isArray(filterValue)) { |
|
153 | if ($.isArray(filterValue)) { | |
154 | option.val(filterValue[1]).text(filterValue[0]); |
|
154 | option.val(filterValue[1]).text(filterValue[0]); | |
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} |
|
155 | if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);} | |
156 | } else { |
|
156 | } else { | |
157 | option.val(filterValue).text(filterValue); |
|
157 | option.val(filterValue).text(filterValue); | |
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} |
|
158 | if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);} | |
159 | } |
|
159 | } | |
160 | select.append(option); |
|
160 | select.append(option); | |
161 | } |
|
161 | } | |
162 | break; |
|
162 | break; | |
163 | case "date": |
|
163 | case "date": | |
164 | case "date_past": |
|
164 | case "date_past": | |
165 | tr.find('td.values').append( |
|
165 | tr.find('td.values').append( | |
166 |
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" |
|
166 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' + | |
167 |
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" |
|
167 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' + | |
168 |
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" |
|
168 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>' | |
169 | ); |
|
169 | ); | |
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); |
|
170 | $('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions); | |
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); |
|
171 | $('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions); | |
172 | $('#values_'+fieldId).val(values[0]); |
|
172 | $('#values_'+fieldId).val(values[0]); | |
173 | break; |
|
173 | break; | |
174 | case "string": |
|
174 | case "string": | |
175 | case "text": |
|
175 | case "text": | |
176 | tr.find('td.values').append( |
|
176 | tr.find('td.values').append( | |
177 |
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" |
|
177 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>' | |
178 | ); |
|
178 | ); | |
179 | $('#values_'+fieldId).val(values[0]); |
|
179 | $('#values_'+fieldId).val(values[0]); | |
180 | break; |
|
180 | break; | |
181 | case "integer": |
|
181 | case "integer": | |
182 | case "float": |
|
182 | case "float": | |
183 | tr.find('td.values').append( |
|
183 | tr.find('td.values').append( | |
184 |
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" |
|
184 | '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' + | |
185 |
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" |
|
185 | ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>' | |
186 | ); |
|
186 | ); | |
187 | $('#values_'+fieldId+'_1').val(values[0]); |
|
187 | $('#values_'+fieldId+'_1').val(values[0]); | |
188 | $('#values_'+fieldId+'_2').val(values[1]); |
|
188 | $('#values_'+fieldId+'_2').val(values[1]); | |
189 | break; |
|
189 | break; | |
190 | } |
|
190 | } | |
191 | } |
|
191 | } | |
192 |
|
192 | |||
193 | function toggleFilter(field) { |
|
193 | function toggleFilter(field) { | |
194 | var fieldId = field.replace('.', '_'); |
|
194 | var fieldId = field.replace('.', '_'); | |
195 | if ($('#cb_' + fieldId).is(':checked')) { |
|
195 | if ($('#cb_' + fieldId).is(':checked')) { | |
196 | $("#operators_" + fieldId).show().removeAttr('disabled'); |
|
196 | $("#operators_" + fieldId).show().removeAttr('disabled'); | |
197 | toggleOperator(field); |
|
197 | toggleOperator(field); | |
198 | } else { |
|
198 | } else { | |
199 | $("#operators_" + fieldId).hide().attr('disabled', true); |
|
199 | $("#operators_" + fieldId).hide().attr('disabled', true); | |
200 | enableValues(field, []); |
|
200 | enableValues(field, []); | |
201 | } |
|
201 | } | |
202 | } |
|
202 | } | |
203 |
|
203 | |||
204 | function enableValues(field, indexes) { |
|
204 | function enableValues(field, indexes) { | |
205 | var fieldId = field.replace('.', '_'); |
|
205 | var fieldId = field.replace('.', '_'); | |
206 | $('#tr_'+fieldId+' td.values .value').each(function(index) { |
|
206 | $('#tr_'+fieldId+' td.values .value').each(function(index) { | |
207 | if ($.inArray(index, indexes) >= 0) { |
|
207 | if ($.inArray(index, indexes) >= 0) { | |
208 | $(this).removeAttr('disabled'); |
|
208 | $(this).removeAttr('disabled'); | |
209 | $(this).parents('span').first().show(); |
|
209 | $(this).parents('span').first().show(); | |
210 | } else { |
|
210 | } else { | |
211 | $(this).val(''); |
|
211 | $(this).val(''); | |
212 | $(this).attr('disabled', true); |
|
212 | $(this).attr('disabled', true); | |
213 | $(this).parents('span').first().hide(); |
|
213 | $(this).parents('span').first().hide(); | |
214 | } |
|
214 | } | |
215 |
|
215 | |||
216 | if ($(this).hasClass('group')) { |
|
216 | if ($(this).hasClass('group')) { | |
217 | $(this).addClass('open'); |
|
217 | $(this).addClass('open'); | |
218 | } else { |
|
218 | } else { | |
219 | $(this).show(); |
|
219 | $(this).show(); | |
220 | } |
|
220 | } | |
221 | }); |
|
221 | }); | |
222 | } |
|
222 | } | |
223 |
|
223 | |||
224 | function toggleOperator(field) { |
|
224 | function toggleOperator(field) { | |
225 | var fieldId = field.replace('.', '_'); |
|
225 | var fieldId = field.replace('.', '_'); | |
226 | var operator = $("#operators_" + fieldId); |
|
226 | var operator = $("#operators_" + fieldId); | |
227 | switch (operator.val()) { |
|
227 | switch (operator.val()) { | |
228 | case "!*": |
|
228 | case "!*": | |
229 | case "*": |
|
229 | case "*": | |
230 | case "t": |
|
230 | case "t": | |
231 | case "w": |
|
231 | case "w": | |
232 | case "o": |
|
232 | case "o": | |
233 | case "c": |
|
233 | case "c": | |
234 | enableValues(field, []); |
|
234 | enableValues(field, []); | |
235 | break; |
|
235 | break; | |
236 | case "><": |
|
236 | case "><": | |
237 | enableValues(field, [0,1]); |
|
237 | enableValues(field, [0,1]); | |
238 | break; |
|
238 | break; | |
239 | case "<t+": |
|
239 | case "<t+": | |
240 | case ">t+": |
|
240 | case ">t+": | |
241 | case "t+": |
|
241 | case "t+": | |
242 | case ">t-": |
|
242 | case ">t-": | |
243 | case "<t-": |
|
243 | case "<t-": | |
244 | case "t-": |
|
244 | case "t-": | |
245 | enableValues(field, [2]); |
|
245 | enableValues(field, [2]); | |
246 | break; |
|
246 | break; | |
247 | default: |
|
247 | default: | |
248 | enableValues(field, [0]); |
|
248 | enableValues(field, [0]); | |
249 | break; |
|
249 | break; | |
250 | } |
|
250 | } | |
251 | } |
|
251 | } | |
252 |
|
252 | |||
253 | function toggleMultiSelect(el) { |
|
253 | function toggleMultiSelect(el) { | |
254 | if (el.attr('multiple')) { |
|
254 | if (el.attr('multiple')) { | |
255 | el.removeAttr('multiple'); |
|
255 | el.removeAttr('multiple'); | |
256 | } else { |
|
256 | } else { | |
257 | el.attr('multiple', true); |
|
257 | el.attr('multiple', true); | |
258 | } |
|
258 | } | |
259 | } |
|
259 | } | |
260 |
|
260 | |||
261 | function submit_query_form(id) { |
|
261 | function submit_query_form(id) { | |
262 | selectAllOptions("selected_columns"); |
|
262 | selectAllOptions("selected_columns"); | |
263 | $('#'+id).submit(); |
|
263 | $('#'+id).submit(); | |
264 | } |
|
264 | } | |
265 |
|
265 | |||
266 | var fileFieldCount = 1; |
|
266 | var fileFieldCount = 1; | |
267 | function addFileField() { |
|
267 | function addFileField() { | |
268 | var fields = $('#attachments_fields'); |
|
268 | var fields = $('#attachments_fields'); | |
269 | if (fields.children().length >= 10) return false; |
|
269 | if (fields.children().length >= 10) return false; | |
270 | fileFieldCount++; |
|
270 | fileFieldCount++; | |
271 | var s = fields.children('span').first().clone(); |
|
271 | var s = fields.children('span').first().clone(); | |
272 | s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val(''); |
|
272 | s.children('input.file').attr('name', "attachments[" + fileFieldCount + "][file]").val(''); | |
273 | s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val(''); |
|
273 | s.children('input.description').attr('name', "attachments[" + fileFieldCount + "][description]").val(''); | |
274 | fields.append(s); |
|
274 | fields.append(s); | |
275 | } |
|
275 | } | |
276 |
|
276 | |||
277 | function removeFileField(el) { |
|
277 | function removeFileField(el) { | |
278 | var fields = $('#attachments_fields'); |
|
278 | var fields = $('#attachments_fields'); | |
279 | var s = $(el).parents('span').first(); |
|
279 | var s = $(el).parents('span').first(); | |
280 | if (fields.children().length > 1) { |
|
280 | if (fields.children().length > 1) { | |
281 | s.remove(); |
|
281 | s.remove(); | |
282 | } else { |
|
282 | } else { | |
283 | s.children('input.file').val(''); |
|
283 | s.children('input.file').val(''); | |
284 | s.children('input.description').val(''); |
|
284 | s.children('input.description').val(''); | |
285 | } |
|
285 | } | |
286 | } |
|
286 | } | |
287 |
|
287 | |||
288 | function checkFileSize(el, maxSize, message) { |
|
288 | function checkFileSize(el, maxSize, message) { | |
289 | var files = el.files; |
|
289 | var files = el.files; | |
290 | if (files) { |
|
290 | if (files) { | |
291 | for (var i=0; i<files.length; i++) { |
|
291 | for (var i=0; i<files.length; i++) { | |
292 | if (files[i].size > maxSize) { |
|
292 | if (files[i].size > maxSize) { | |
293 | alert(message); |
|
293 | alert(message); | |
294 | el.value = ""; |
|
294 | el.value = ""; | |
295 | } |
|
295 | } | |
296 | } |
|
296 | } | |
297 | } |
|
297 | } | |
298 | } |
|
298 | } | |
299 |
|
299 | |||
300 | function showTab(name) { |
|
300 | function showTab(name) { | |
301 | $('div#content .tab-content').hide(); |
|
301 | $('div#content .tab-content').hide(); | |
302 | $('div.tabs a').removeClass('selected'); |
|
302 | $('div.tabs a').removeClass('selected'); | |
303 | $('#tab-content-' + name).show(); |
|
303 | $('#tab-content-' + name).show(); | |
304 | $('#tab-' + name).addClass('selected'); |
|
304 | $('#tab-' + name).addClass('selected'); | |
305 | return false; |
|
305 | return false; | |
306 | } |
|
306 | } | |
307 |
|
307 | |||
308 | function moveTabRight(el) { |
|
308 | function moveTabRight(el) { | |
309 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
309 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
310 | var tabsWidth = 0; |
|
310 | var tabsWidth = 0; | |
311 | var i = 0; |
|
311 | var i = 0; | |
312 | lis.each(function(){ |
|
312 | lis.each(function(){ | |
313 | if ($(this).is(':visible')) { |
|
313 | if ($(this).is(':visible')) { | |
314 | tabsWidth += $(this).width() + 6; |
|
314 | tabsWidth += $(this).width() + 6; | |
315 | } |
|
315 | } | |
316 | }); |
|
316 | }); | |
317 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } |
|
317 | if (tabsWidth < $(el).parents('div.tabs').first().width() - 60) { return; } | |
318 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
318 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
319 | lis.eq(i).hide(); |
|
319 | lis.eq(i).hide(); | |
320 | } |
|
320 | } | |
321 |
|
321 | |||
322 | function moveTabLeft(el) { |
|
322 | function moveTabLeft(el) { | |
323 | var lis = $(el).parents('div.tabs').first().find('ul').children(); |
|
323 | var lis = $(el).parents('div.tabs').first().find('ul').children(); | |
324 | var i = 0; |
|
324 | var i = 0; | |
325 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } |
|
325 | while (i<lis.length && !lis.eq(i).is(':visible')) { i++; } | |
326 | if (i>0) { |
|
326 | if (i>0) { | |
327 | lis.eq(i-1).show(); |
|
327 | lis.eq(i-1).show(); | |
328 | } |
|
328 | } | |
329 | } |
|
329 | } | |
330 |
|
330 | |||
331 | function displayTabsButtons() { |
|
331 | function displayTabsButtons() { | |
332 | var lis; |
|
332 | var lis; | |
333 | var tabsWidth = 0; |
|
333 | var tabsWidth = 0; | |
334 | var el; |
|
334 | var el; | |
335 | $('div.tabs').each(function() { |
|
335 | $('div.tabs').each(function() { | |
336 | el = $(this); |
|
336 | el = $(this); | |
337 | lis = el.find('ul').children(); |
|
337 | lis = el.find('ul').children(); | |
338 | lis.each(function(){ |
|
338 | lis.each(function(){ | |
339 | if ($(this).is(':visible')) { |
|
339 | if ($(this).is(':visible')) { | |
340 | tabsWidth += $(this).width() + 6; |
|
340 | tabsWidth += $(this).width() + 6; | |
341 | } |
|
341 | } | |
342 | }); |
|
342 | }); | |
343 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { |
|
343 | if ((tabsWidth < el.width() - 60) && (lis.first().is(':visible'))) { | |
344 | el.find('div.tabs-buttons').hide(); |
|
344 | el.find('div.tabs-buttons').hide(); | |
345 | } else { |
|
345 | } else { | |
346 | el.find('div.tabs-buttons').show(); |
|
346 | el.find('div.tabs-buttons').show(); | |
347 | } |
|
347 | } | |
348 | }); |
|
348 | }); | |
349 | } |
|
349 | } | |
350 |
|
350 | |||
351 | function setPredecessorFieldsVisibility() { |
|
351 | function setPredecessorFieldsVisibility() { | |
352 | var relationType = $('#relation_relation_type'); |
|
352 | var relationType = $('#relation_relation_type'); | |
353 | if (relationType.val() == "precedes" || relationType.val() == "follows") { |
|
353 | if (relationType.val() == "precedes" || relationType.val() == "follows") { | |
354 | $('#predecessor_fields').show(); |
|
354 | $('#predecessor_fields').show(); | |
355 | } else { |
|
355 | } else { | |
356 | $('#predecessor_fields').hide(); |
|
356 | $('#predecessor_fields').hide(); | |
357 | } |
|
357 | } | |
358 | } |
|
358 | } | |
359 |
|
359 | |||
360 | function showModal(id, width) { |
|
360 | function showModal(id, width) { | |
361 | var el = $('#'+id).first(); |
|
361 | var el = $('#'+id).first(); | |
362 | if (el.length == 0 || el.is(':visible')) {return;} |
|
362 | if (el.length == 0 || el.is(':visible')) {return;} | |
363 | var title = el.find('h3.title').text(); |
|
363 | var title = el.find('h3.title').text(); | |
364 | el.dialog({ |
|
364 | el.dialog({ | |
365 | width: width, |
|
365 | width: width, | |
366 | modal: true, |
|
366 | modal: true, | |
367 | resizable: false, |
|
367 | resizable: false, | |
368 | dialogClass: 'modal', |
|
368 | dialogClass: 'modal', | |
369 | title: title |
|
369 | title: title | |
370 | }); |
|
370 | }); | |
371 | el.find("input[type=text], input[type=submit]").first().focus(); |
|
371 | el.find("input[type=text], input[type=submit]").first().focus(); | |
372 | } |
|
372 | } | |
373 |
|
373 | |||
374 | function hideModal(el) { |
|
374 | function hideModal(el) { | |
375 | var modal; |
|
375 | var modal; | |
376 | if (el) { |
|
376 | if (el) { | |
377 | modal = $(el).parents('.ui-dialog-content'); |
|
377 | modal = $(el).parents('.ui-dialog-content'); | |
378 | } else { |
|
378 | } else { | |
379 | modal = $('#ajax-modal'); |
|
379 | modal = $('#ajax-modal'); | |
380 | } |
|
380 | } | |
381 | modal.dialog("close"); |
|
381 | modal.dialog("close"); | |
382 | } |
|
382 | } | |
383 |
|
383 | |||
384 | function submitPreview(url, form, target) { |
|
384 | function submitPreview(url, form, target) { | |
385 | $.ajax({ |
|
385 | $.ajax({ | |
386 | url: url, |
|
386 | url: url, | |
387 | type: 'post', |
|
387 | type: 'post', | |
388 | data: $('#'+form).serialize(), |
|
388 | data: $('#'+form).serialize(), | |
389 | success: function(data){ |
|
389 | success: function(data){ | |
390 | $('#'+target).html(data); |
|
390 | $('#'+target).html(data); | |
391 | } |
|
391 | } | |
392 | }); |
|
392 | }); | |
393 | } |
|
393 | } | |
394 |
|
394 | |||
395 | function collapseScmEntry(id) { |
|
395 | function collapseScmEntry(id) { | |
396 | $('.'+id).each(function() { |
|
396 | $('.'+id).each(function() { | |
397 | if ($(this).hasClass('open')) { |
|
397 | if ($(this).hasClass('open')) { | |
398 | collapseScmEntry($(this).attr('id')); |
|
398 | collapseScmEntry($(this).attr('id')); | |
399 | } |
|
399 | } | |
400 | $(this).hide(); |
|
400 | $(this).hide(); | |
401 | }); |
|
401 | }); | |
402 | $('#'+id).removeClass('open'); |
|
402 | $('#'+id).removeClass('open'); | |
403 | } |
|
403 | } | |
404 |
|
404 | |||
405 | function expandScmEntry(id) { |
|
405 | function expandScmEntry(id) { | |
406 | $('.'+id).each(function() { |
|
406 | $('.'+id).each(function() { | |
407 | $(this).show(); |
|
407 | $(this).show(); | |
408 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { |
|
408 | if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) { | |
409 | expandScmEntry($(this).attr('id')); |
|
409 | expandScmEntry($(this).attr('id')); | |
410 | } |
|
410 | } | |
411 | }); |
|
411 | }); | |
412 | $('#'+id).addClass('open'); |
|
412 | $('#'+id).addClass('open'); | |
413 | } |
|
413 | } | |
414 |
|
414 | |||
415 | function scmEntryClick(id, url) { |
|
415 | function scmEntryClick(id, url) { | |
416 | el = $('#'+id); |
|
416 | el = $('#'+id); | |
417 | if (el.hasClass('open')) { |
|
417 | if (el.hasClass('open')) { | |
418 | collapseScmEntry(id); |
|
418 | collapseScmEntry(id); | |
419 | el.addClass('collapsed'); |
|
419 | el.addClass('collapsed'); | |
420 | return false; |
|
420 | return false; | |
421 | } else if (el.hasClass('loaded')) { |
|
421 | } else if (el.hasClass('loaded')) { | |
422 | expandScmEntry(id); |
|
422 | expandScmEntry(id); | |
423 | el.removeClass('collapsed'); |
|
423 | el.removeClass('collapsed'); | |
424 | return false; |
|
424 | return false; | |
425 | } |
|
425 | } | |
426 | if (el.hasClass('loading')) { |
|
426 | if (el.hasClass('loading')) { | |
427 | return false; |
|
427 | return false; | |
428 | } |
|
428 | } | |
429 | el.addClass('loading'); |
|
429 | el.addClass('loading'); | |
430 | $.ajax({ |
|
430 | $.ajax({ | |
431 | url: url, |
|
431 | url: url, | |
432 | success: function(data){ |
|
432 | success: function(data){ | |
433 | el.after(data); |
|
433 | el.after(data); | |
434 | el.addClass('open').addClass('loaded').removeClass('loading'); |
|
434 | el.addClass('open').addClass('loaded').removeClass('loading'); | |
435 | } |
|
435 | } | |
436 | }); |
|
436 | }); | |
437 | return true; |
|
437 | return true; | |
438 | } |
|
438 | } | |
439 |
|
439 | |||
440 | function randomKey(size) { |
|
440 | function randomKey(size) { | |
441 | var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); |
|
441 | var chars = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'); | |
442 | var key = ''; |
|
442 | var key = ''; | |
443 | for (i = 0; i < size; i++) { |
|
443 | for (i = 0; i < size; i++) { | |
444 | key += chars[Math.floor(Math.random() * chars.length)]; |
|
444 | key += chars[Math.floor(Math.random() * chars.length)]; | |
445 | } |
|
445 | } | |
446 | return key; |
|
446 | return key; | |
447 | } |
|
447 | } | |
448 |
|
448 | |||
449 | // Can't use Rails' remote select because we need the form data |
|
449 | // Can't use Rails' remote select because we need the form data | |
450 | function updateIssueFrom(url) { |
|
450 | function updateIssueFrom(url) { | |
451 | $.ajax({ |
|
451 | $.ajax({ | |
452 | url: url, |
|
452 | url: url, | |
453 | type: 'post', |
|
453 | type: 'post', | |
454 | data: $('#issue-form').serialize() |
|
454 | data: $('#issue-form').serialize() | |
455 | }); |
|
455 | }); | |
456 | } |
|
456 | } | |
457 |
|
457 | |||
458 | function updateBulkEditFrom(url) { |
|
458 | function updateBulkEditFrom(url) { | |
459 | $.ajax({ |
|
459 | $.ajax({ | |
460 | url: url, |
|
460 | url: url, | |
461 | type: 'post', |
|
461 | type: 'post', | |
462 | data: $('#bulk_edit_form').serialize() |
|
462 | data: $('#bulk_edit_form').serialize() | |
463 | }); |
|
463 | }); | |
464 | } |
|
464 | } | |
465 |
|
465 | |||
466 | function observeAutocompleteField(fieldId, url) { |
|
466 | function observeAutocompleteField(fieldId, url) { | |
467 | $('#'+fieldId).autocomplete({ |
|
467 | $('#'+fieldId).autocomplete({ | |
468 | source: url, |
|
468 | source: url, | |
469 | minLength: 2, |
|
469 | minLength: 2, | |
470 | }); |
|
470 | }); | |
471 | } |
|
471 | } | |
472 |
|
472 | |||
473 | function observeSearchfield(fieldId, targetId, url) { |
|
473 | function observeSearchfield(fieldId, targetId, url) { | |
474 | $('#'+fieldId).each(function() { |
|
474 | $('#'+fieldId).each(function() { | |
475 | var $this = $(this); |
|
475 | var $this = $(this); | |
476 | $this.attr('data-value-was', $this.val()); |
|
476 | $this.attr('data-value-was', $this.val()); | |
477 | var check = function() { |
|
477 | var check = function() { | |
478 | var val = $this.val(); |
|
478 | var val = $this.val(); | |
479 | if ($this.attr('data-value-was') != val){ |
|
479 | if ($this.attr('data-value-was') != val){ | |
480 | $this.attr('data-value-was', val); |
|
480 | $this.attr('data-value-was', val); | |
481 | $.ajax({ |
|
481 | $.ajax({ | |
482 | url: url, |
|
482 | url: url, | |
483 | type: 'get', |
|
483 | type: 'get', | |
484 | data: {q: $this.val()}, |
|
484 | data: {q: $this.val()}, | |
485 | success: function(data){ $('#'+targetId).html(data); }, |
|
485 | success: function(data){ $('#'+targetId).html(data); }, | |
486 | beforeSend: function(){ $this.addClass('ajax-loading'); }, |
|
486 | beforeSend: function(){ $this.addClass('ajax-loading'); }, | |
487 | complete: function(){ $this.removeClass('ajax-loading'); } |
|
487 | complete: function(){ $this.removeClass('ajax-loading'); } | |
488 | }); |
|
488 | }); | |
489 | } |
|
489 | } | |
490 | }; |
|
490 | }; | |
491 | var reset = function() { |
|
491 | var reset = function() { | |
492 | if (timer) { |
|
492 | if (timer) { | |
493 | clearInterval(timer); |
|
493 | clearInterval(timer); | |
494 | timer = setInterval(check, 300); |
|
494 | timer = setInterval(check, 300); | |
495 | } |
|
495 | } | |
496 | }; |
|
496 | }; | |
497 | var timer = setInterval(check, 300); |
|
497 | var timer = setInterval(check, 300); | |
498 | $this.bind('keyup click mousemove', reset); |
|
498 | $this.bind('keyup click mousemove', reset); | |
499 | }); |
|
499 | }); | |
500 | } |
|
500 | } | |
501 |
|
501 | |||
502 | function observeProjectModules() { |
|
502 | function observeProjectModules() { | |
503 | var f = function() { |
|
503 | var f = function() { | |
504 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ |
|
504 | /* Hides trackers and issues custom fields on the new project form when issue_tracking module is disabled */ | |
505 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { |
|
505 | if ($('#project_enabled_module_names_issue_tracking').attr('checked')) { | |
506 | $('#project_trackers').show(); |
|
506 | $('#project_trackers').show(); | |
507 | }else{ |
|
507 | }else{ | |
508 | $('#project_trackers').hide(); |
|
508 | $('#project_trackers').hide(); | |
509 | } |
|
509 | } | |
510 | }; |
|
510 | }; | |
511 |
|
511 | |||
512 | $(window).load(f); |
|
512 | $(window).load(f); | |
513 | $('#project_enabled_module_names_issue_tracking').change(f); |
|
513 | $('#project_enabled_module_names_issue_tracking').change(f); | |
514 | } |
|
514 | } | |
515 |
|
515 | |||
516 | function initMyPageSortable(list, url) { |
|
516 | function initMyPageSortable(list, url) { | |
517 | $('#list-'+list).sortable({ |
|
517 | $('#list-'+list).sortable({ | |
518 | connectWith: '.block-receiver', |
|
518 | connectWith: '.block-receiver', | |
519 | tolerance: 'pointer', |
|
519 | tolerance: 'pointer', | |
520 | update: function(){ |
|
520 | update: function(){ | |
521 | $.ajax({ |
|
521 | $.ajax({ | |
522 | url: url, |
|
522 | url: url, | |
523 | type: 'post', |
|
523 | type: 'post', | |
524 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} |
|
524 | data: {'blocks': $.map($('#list-'+list).children(), function(el){return $(el).attr('id');})} | |
525 | }); |
|
525 | }); | |
526 | } |
|
526 | } | |
527 | }); |
|
527 | }); | |
528 | $("#list-top, #list-left, #list-right").disableSelection(); |
|
528 | $("#list-top, #list-left, #list-right").disableSelection(); | |
529 | } |
|
529 | } | |
530 |
|
530 | |||
531 | var warnLeavingUnsavedMessage; |
|
531 | var warnLeavingUnsavedMessage; | |
532 | function warnLeavingUnsaved(message) { |
|
532 | function warnLeavingUnsaved(message) { | |
533 | warnLeavingUnsavedMessage = message; |
|
533 | warnLeavingUnsavedMessage = message; | |
534 |
|
534 | |||
535 | $('form').submit(function(){ |
|
535 | $('form').submit(function(){ | |
536 | $('textarea').removeData('changed'); |
|
536 | $('textarea').removeData('changed'); | |
537 | }); |
|
537 | }); | |
538 | $('textarea').change(function(){ |
|
538 | $('textarea').change(function(){ | |
539 | $(this).data('changed', 'changed'); |
|
539 | $(this).data('changed', 'changed'); | |
540 | }); |
|
540 | }); | |
541 | window.onbeforeunload = function(){ |
|
541 | window.onbeforeunload = function(){ | |
542 | var warn = false; |
|
542 | var warn = false; | |
543 | $('textarea').blur().each(function(){ |
|
543 | $('textarea').blur().each(function(){ | |
544 | if ($(this).data('changed')) { |
|
544 | if ($(this).data('changed')) { | |
545 | warn = true; |
|
545 | warn = true; | |
546 | } |
|
546 | } | |
547 | }); |
|
547 | }); | |
548 | if (warn) {return warnLeavingUnsavedMessage;} |
|
548 | if (warn) {return warnLeavingUnsavedMessage;} | |
549 | }; |
|
549 | }; | |
550 | }; |
|
550 | }; | |
551 |
|
551 | |||
552 | $(document).ready(function(){ |
|
552 | $(document).ready(function(){ | |
553 | $('#ajax-indicator').bind('ajaxSend', function(){ |
|
553 | $('#ajax-indicator').bind('ajaxSend', function(){ | |
554 | if ($('.ajax-loading').length == 0) { |
|
554 | if ($('.ajax-loading').length == 0) { | |
555 | $('#ajax-indicator').show(); |
|
555 | $('#ajax-indicator').show(); | |
556 | } |
|
556 | } | |
557 | }); |
|
557 | }); | |
558 | $('#ajax-indicator').bind('ajaxStop', function(){ |
|
558 | $('#ajax-indicator').bind('ajaxStop', function(){ | |
559 | $('#ajax-indicator').hide(); |
|
559 | $('#ajax-indicator').hide(); | |
560 | }); |
|
560 | }); | |
561 | }); |
|
561 | }); | |
562 |
|
562 | |||
563 | function hideOnLoad() { |
|
563 | function hideOnLoad() { | |
564 | $('.hol').hide(); |
|
564 | $('.hol').hide(); | |
565 | } |
|
565 | } | |
566 |
|
566 | |||
567 | function addFormObserversForDoubleSubmit() { |
|
567 | function addFormObserversForDoubleSubmit() { | |
568 | $('form[method=post]').each(function() { |
|
568 | $('form[method=post]').each(function() { | |
569 | if (!$(this).hasClass('multiple-submit')) { |
|
569 | if (!$(this).hasClass('multiple-submit')) { | |
570 | $(this).submit(function(form_submission) { |
|
570 | $(this).submit(function(form_submission) { | |
571 | if ($(form_submission.target).attr('data-submitted')) { |
|
571 | if ($(form_submission.target).attr('data-submitted')) { | |
572 | form_submission.preventDefault(); |
|
572 | form_submission.preventDefault(); | |
573 | } else { |
|
573 | } else { | |
574 | $(form_submission.target).attr('data-submitted', true); |
|
574 | $(form_submission.target).attr('data-submitted', true); | |
575 | } |
|
575 | } | |
576 | }); |
|
576 | }); | |
577 | } |
|
577 | } | |
578 | }); |
|
578 | }); | |
579 | } |
|
579 | } | |
580 |
|
580 | |||
581 | $(document).ready(hideOnLoad); |
|
581 | $(document).ready(hideOnLoad); | |
582 | $(document).ready(addFormObserversForDoubleSubmit); |
|
582 | $(document).ready(addFormObserversForDoubleSubmit); |
@@ -1,276 +1,284 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; either version 2 |
|
6 | # as published by the Free Software Foundation; either version 2 | |
7 | # of the License, or (at your option) any later version. |
|
7 | # of the License, or (at your option) any later version. | |
8 | # |
|
8 | # | |
9 | # This program is distributed in the hope that it will be useful, |
|
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. |
|
12 | # GNU General Public License for more details. | |
13 | # |
|
13 | # | |
14 | # You should have received a copy of the GNU General Public License |
|
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software |
|
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | require File.expand_path('../../test_helper', __FILE__) |
|
18 | require File.expand_path('../../test_helper', __FILE__) | |
19 |
|
19 | |||
20 | class QueriesControllerTest < ActionController::TestCase |
|
20 | class QueriesControllerTest < ActionController::TestCase | |
21 | fixtures :projects, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries |
|
21 | fixtures :projects, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries | |
22 |
|
22 | |||
23 | def setup |
|
23 | def setup | |
24 | User.current = nil |
|
24 | User.current = nil | |
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | def test_new_project_query |
|
27 | def test_new_project_query | |
28 | @request.session[:user_id] = 2 |
|
28 | @request.session[:user_id] = 2 | |
29 | get :new, :project_id => 1 |
|
29 | get :new, :project_id => 1 | |
30 | assert_response :success |
|
30 | assert_response :success | |
31 | assert_template 'new' |
|
31 | assert_template 'new' | |
32 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
32 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
33 | :name => 'query[is_public]', |
|
33 | :name => 'query[is_public]', | |
34 | :checked => nil } |
|
34 | :checked => nil } | |
35 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
35 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
36 | :name => 'query_is_for_all', |
|
36 | :name => 'query_is_for_all', | |
37 | :checked => nil, |
|
37 | :checked => nil, | |
38 | :disabled => nil } |
|
38 | :disabled => nil } | |
39 | assert_select 'select[name=?]', 'c[]' do |
|
39 | assert_select 'select[name=?]', 'c[]' do | |
40 | assert_select 'option[value=tracker]' |
|
40 | assert_select 'option[value=tracker]' | |
41 | assert_select 'option[value=subject]' |
|
41 | assert_select 'option[value=subject]' | |
42 | end |
|
42 | end | |
43 | end |
|
43 | end | |
44 |
|
44 | |||
45 | def test_new_global_query |
|
45 | def test_new_global_query | |
46 | @request.session[:user_id] = 2 |
|
46 | @request.session[:user_id] = 2 | |
47 | get :new |
|
47 | get :new | |
48 | assert_response :success |
|
48 | assert_response :success | |
49 | assert_template 'new' |
|
49 | assert_template 'new' | |
50 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
50 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
51 | :name => 'query[is_public]' } |
|
51 | :name => 'query[is_public]' } | |
52 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
52 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
53 | :name => 'query_is_for_all', |
|
53 | :name => 'query_is_for_all', | |
54 | :checked => 'checked', |
|
54 | :checked => 'checked', | |
55 | :disabled => nil } |
|
55 | :disabled => nil } | |
56 | end |
|
56 | end | |
57 |
|
57 | |||
58 | def test_new_on_invalid_project |
|
58 | def test_new_on_invalid_project | |
59 | @request.session[:user_id] = 2 |
|
59 | @request.session[:user_id] = 2 | |
60 | get :new, :project_id => 'invalid' |
|
60 | get :new, :project_id => 'invalid' | |
61 | assert_response 404 |
|
61 | assert_response 404 | |
62 | end |
|
62 | end | |
63 |
|
63 | |||
64 | def test_create_project_public_query |
|
64 | def test_create_project_public_query | |
65 | @request.session[:user_id] = 2 |
|
65 | @request.session[:user_id] = 2 | |
66 | post :create, |
|
66 | post :create, | |
67 | :project_id => 'ecookbook', |
|
67 | :project_id => 'ecookbook', | |
68 | :default_columns => '1', |
|
68 | :default_columns => '1', | |
69 | :f => ["status_id", "assigned_to_id"], |
|
69 | :f => ["status_id", "assigned_to_id"], | |
70 | :op => {"assigned_to_id" => "=", "status_id" => "o"}, |
|
70 | :op => {"assigned_to_id" => "=", "status_id" => "o"}, | |
71 | :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, |
|
71 | :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, | |
72 | :query => {"name" => "test_new_project_public_query", "is_public" => "1"} |
|
72 | :query => {"name" => "test_new_project_public_query", "is_public" => "1"} | |
73 |
|
73 | |||
74 | q = Query.find_by_name('test_new_project_public_query') |
|
74 | q = Query.find_by_name('test_new_project_public_query') | |
75 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q |
|
75 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q | |
76 | assert q.is_public? |
|
76 | assert q.is_public? | |
77 | assert q.has_default_columns? |
|
77 | assert q.has_default_columns? | |
78 | assert q.valid? |
|
78 | assert q.valid? | |
79 | end |
|
79 | end | |
80 |
|
80 | |||
81 | def test_create_project_private_query |
|
81 | def test_create_project_private_query | |
82 | @request.session[:user_id] = 3 |
|
82 | @request.session[:user_id] = 3 | |
83 | post :create, |
|
83 | post :create, | |
84 | :project_id => 'ecookbook', |
|
84 | :project_id => 'ecookbook', | |
85 | :default_columns => '1', |
|
85 | :default_columns => '1', | |
86 | :fields => ["status_id", "assigned_to_id"], |
|
86 | :fields => ["status_id", "assigned_to_id"], | |
87 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, |
|
87 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, | |
88 | :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, |
|
88 | :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, | |
89 | :query => {"name" => "test_new_project_private_query", "is_public" => "1"} |
|
89 | :query => {"name" => "test_new_project_private_query", "is_public" => "1"} | |
90 |
|
90 | |||
91 | q = Query.find_by_name('test_new_project_private_query') |
|
91 | q = Query.find_by_name('test_new_project_private_query') | |
92 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q |
|
92 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :query_id => q | |
93 | assert !q.is_public? |
|
93 | assert !q.is_public? | |
94 | assert q.has_default_columns? |
|
94 | assert q.has_default_columns? | |
95 | assert q.valid? |
|
95 | assert q.valid? | |
96 | end |
|
96 | end | |
97 |
|
97 | |||
98 | def test_create_global_private_query_with_custom_columns |
|
98 | def test_create_global_private_query_with_custom_columns | |
99 | @request.session[:user_id] = 3 |
|
99 | @request.session[:user_id] = 3 | |
100 | post :create, |
|
100 | post :create, | |
101 | :fields => ["status_id", "assigned_to_id"], |
|
101 | :fields => ["status_id", "assigned_to_id"], | |
102 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, |
|
102 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, | |
103 | :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, |
|
103 | :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, | |
104 | :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, |
|
104 | :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, | |
105 | :c => ["", "tracker", "subject", "priority", "category"] |
|
105 | :c => ["", "tracker", "subject", "priority", "category"] | |
106 |
|
106 | |||
107 | q = Query.find_by_name('test_new_global_private_query') |
|
107 | q = Query.find_by_name('test_new_global_private_query') | |
108 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q |
|
108 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q | |
109 | assert !q.is_public? |
|
109 | assert !q.is_public? | |
110 | assert !q.has_default_columns? |
|
110 | assert !q.has_default_columns? | |
111 | assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} |
|
111 | assert_equal [:tracker, :subject, :priority, :category], q.columns.collect {|c| c.name} | |
112 | assert q.valid? |
|
112 | assert q.valid? | |
113 | end |
|
113 | end | |
114 |
|
114 | |||
115 | def test_create_global_query_with_custom_filters |
|
115 | def test_create_global_query_with_custom_filters | |
116 | @request.session[:user_id] = 3 |
|
116 | @request.session[:user_id] = 3 | |
117 | post :create, |
|
117 | post :create, | |
118 | :fields => ["assigned_to_id"], |
|
118 | :fields => ["assigned_to_id"], | |
119 | :operators => {"assigned_to_id" => "="}, |
|
119 | :operators => {"assigned_to_id" => "="}, | |
120 | :values => { "assigned_to_id" => ["me"]}, |
|
120 | :values => { "assigned_to_id" => ["me"]}, | |
121 | :query => {"name" => "test_new_global_query"} |
|
121 | :query => {"name" => "test_new_global_query"} | |
122 |
|
122 | |||
123 | q = Query.find_by_name('test_new_global_query') |
|
123 | q = Query.find_by_name('test_new_global_query') | |
124 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q |
|
124 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q | |
125 | assert !q.has_filter?(:status_id) |
|
125 | assert !q.has_filter?(:status_id) | |
126 | assert_equal ['assigned_to_id'], q.filters.keys |
|
126 | assert_equal ['assigned_to_id'], q.filters.keys | |
127 | assert q.valid? |
|
127 | assert q.valid? | |
128 | end |
|
128 | end | |
129 |
|
129 | |||
130 | def test_create_with_sort |
|
130 | def test_create_with_sort | |
131 | @request.session[:user_id] = 1 |
|
131 | @request.session[:user_id] = 1 | |
132 | post :create, |
|
132 | post :create, | |
133 | :default_columns => '1', |
|
133 | :default_columns => '1', | |
134 | :operators => {"status_id" => "o"}, |
|
134 | :operators => {"status_id" => "o"}, | |
135 | :values => {"status_id" => ["1"]}, |
|
135 | :values => {"status_id" => ["1"]}, | |
136 | :query => {:name => "test_new_with_sort", |
|
136 | :query => {:name => "test_new_with_sort", | |
137 | :is_public => "1", |
|
137 | :is_public => "1", | |
138 | :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}} |
|
138 | :sort_criteria => {"0" => ["due_date", "desc"], "1" => ["tracker", ""]}} | |
139 |
|
139 | |||
140 | query = Query.find_by_name("test_new_with_sort") |
|
140 | query = Query.find_by_name("test_new_with_sort") | |
141 | assert_not_nil query |
|
141 | assert_not_nil query | |
142 | assert_equal [['due_date', 'desc'], ['tracker', 'asc']], query.sort_criteria |
|
142 | assert_equal [['due_date', 'desc'], ['tracker', 'asc']], query.sort_criteria | |
143 | end |
|
143 | end | |
144 |
|
144 | |||
145 | def test_create_with_failure |
|
145 | def test_create_with_failure | |
146 | @request.session[:user_id] = 2 |
|
146 | @request.session[:user_id] = 2 | |
147 | assert_no_difference '::Query.count' do |
|
147 | assert_no_difference '::Query.count' do | |
148 | post :create, :project_id => 'ecookbook', :query => {:name => ''} |
|
148 | post :create, :project_id => 'ecookbook', :query => {:name => ''} | |
149 | end |
|
149 | end | |
150 | assert_response :success |
|
150 | assert_response :success | |
151 | assert_template 'new' |
|
151 | assert_template 'new' | |
152 | assert_select 'input[name=?]', 'query[name]' |
|
152 | assert_select 'input[name=?]', 'query[name]' | |
153 | end |
|
153 | end | |
154 |
|
154 | |||
155 | def test_edit_global_public_query |
|
155 | def test_edit_global_public_query | |
156 | @request.session[:user_id] = 1 |
|
156 | @request.session[:user_id] = 1 | |
157 | get :edit, :id => 4 |
|
157 | get :edit, :id => 4 | |
158 | assert_response :success |
|
158 | assert_response :success | |
159 | assert_template 'edit' |
|
159 | assert_template 'edit' | |
160 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
160 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
161 | :name => 'query[is_public]', |
|
161 | :name => 'query[is_public]', | |
162 | :checked => 'checked' } |
|
162 | :checked => 'checked' } | |
163 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
163 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
164 | :name => 'query_is_for_all', |
|
164 | :name => 'query_is_for_all', | |
165 | :checked => 'checked', |
|
165 | :checked => 'checked', | |
166 | :disabled => 'disabled' } |
|
166 | :disabled => 'disabled' } | |
167 | end |
|
167 | end | |
168 |
|
168 | |||
169 | def test_edit_global_private_query |
|
169 | def test_edit_global_private_query | |
170 | @request.session[:user_id] = 3 |
|
170 | @request.session[:user_id] = 3 | |
171 | get :edit, :id => 3 |
|
171 | get :edit, :id => 3 | |
172 | assert_response :success |
|
172 | assert_response :success | |
173 | assert_template 'edit' |
|
173 | assert_template 'edit' | |
174 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
174 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
175 | :name => 'query[is_public]' } |
|
175 | :name => 'query[is_public]' } | |
176 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
176 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
177 | :name => 'query_is_for_all', |
|
177 | :name => 'query_is_for_all', | |
178 | :checked => 'checked', |
|
178 | :checked => 'checked', | |
179 | :disabled => 'disabled' } |
|
179 | :disabled => 'disabled' } | |
180 | end |
|
180 | end | |
181 |
|
181 | |||
182 | def test_edit_project_private_query |
|
182 | def test_edit_project_private_query | |
183 | @request.session[:user_id] = 3 |
|
183 | @request.session[:user_id] = 3 | |
184 | get :edit, :id => 2 |
|
184 | get :edit, :id => 2 | |
185 | assert_response :success |
|
185 | assert_response :success | |
186 | assert_template 'edit' |
|
186 | assert_template 'edit' | |
187 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
187 | assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
188 | :name => 'query[is_public]' } |
|
188 | :name => 'query[is_public]' } | |
189 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
189 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
190 | :name => 'query_is_for_all', |
|
190 | :name => 'query_is_for_all', | |
191 | :checked => nil, |
|
191 | :checked => nil, | |
192 | :disabled => nil } |
|
192 | :disabled => nil } | |
193 | end |
|
193 | end | |
194 |
|
194 | |||
195 | def test_edit_project_public_query |
|
195 | def test_edit_project_public_query | |
196 | @request.session[:user_id] = 2 |
|
196 | @request.session[:user_id] = 2 | |
197 | get :edit, :id => 1 |
|
197 | get :edit, :id => 1 | |
198 | assert_response :success |
|
198 | assert_response :success | |
199 | assert_template 'edit' |
|
199 | assert_template 'edit' | |
200 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
200 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
201 | :name => 'query[is_public]', |
|
201 | :name => 'query[is_public]', | |
202 | :checked => 'checked' |
|
202 | :checked => 'checked' | |
203 | } |
|
203 | } | |
204 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', |
|
204 | assert_tag :tag => 'input', :attributes => { :type => 'checkbox', | |
205 | :name => 'query_is_for_all', |
|
205 | :name => 'query_is_for_all', | |
206 | :checked => nil, |
|
206 | :checked => nil, | |
207 | :disabled => 'disabled' } |
|
207 | :disabled => 'disabled' } | |
208 | end |
|
208 | end | |
209 |
|
209 | |||
210 | def test_edit_sort_criteria |
|
210 | def test_edit_sort_criteria | |
211 | @request.session[:user_id] = 1 |
|
211 | @request.session[:user_id] = 1 | |
212 | get :edit, :id => 5 |
|
212 | get :edit, :id => 5 | |
213 | assert_response :success |
|
213 | assert_response :success | |
214 | assert_template 'edit' |
|
214 | assert_template 'edit' | |
215 | assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, |
|
215 | assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, | |
216 | :child => { :tag => 'option', :attributes => { :value => 'priority', |
|
216 | :child => { :tag => 'option', :attributes => { :value => 'priority', | |
217 | :selected => 'selected' } } |
|
217 | :selected => 'selected' } } | |
218 | assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, |
|
218 | assert_tag :tag => 'select', :attributes => { :name => 'query[sort_criteria][0][]' }, | |
219 | :child => { :tag => 'option', :attributes => { :value => 'desc', |
|
219 | :child => { :tag => 'option', :attributes => { :value => 'desc', | |
220 | :selected => 'selected' } } |
|
220 | :selected => 'selected' } } | |
221 | end |
|
221 | end | |
222 |
|
222 | |||
223 | def test_edit_invalid_query |
|
223 | def test_edit_invalid_query | |
224 | @request.session[:user_id] = 2 |
|
224 | @request.session[:user_id] = 2 | |
225 | get :edit, :id => 99 |
|
225 | get :edit, :id => 99 | |
226 | assert_response 404 |
|
226 | assert_response 404 | |
227 | end |
|
227 | end | |
228 |
|
228 | |||
229 | def test_udpate_global_private_query |
|
229 | def test_udpate_global_private_query | |
230 | @request.session[:user_id] = 3 |
|
230 | @request.session[:user_id] = 3 | |
231 | put :update, |
|
231 | put :update, | |
232 | :id => 3, |
|
232 | :id => 3, | |
233 | :default_columns => '1', |
|
233 | :default_columns => '1', | |
234 | :fields => ["status_id", "assigned_to_id"], |
|
234 | :fields => ["status_id", "assigned_to_id"], | |
235 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, |
|
235 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, | |
236 | :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, |
|
236 | :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, | |
237 | :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} |
|
237 | :query => {"name" => "test_edit_global_private_query", "is_public" => "1"} | |
238 |
|
238 | |||
239 | assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 |
|
239 | assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 3 | |
240 | q = Query.find_by_name('test_edit_global_private_query') |
|
240 | q = Query.find_by_name('test_edit_global_private_query') | |
241 | assert !q.is_public? |
|
241 | assert !q.is_public? | |
242 | assert q.has_default_columns? |
|
242 | assert q.has_default_columns? | |
243 | assert q.valid? |
|
243 | assert q.valid? | |
244 | end |
|
244 | end | |
245 |
|
245 | |||
246 | def test_update_global_public_query |
|
246 | def test_update_global_public_query | |
247 | @request.session[:user_id] = 1 |
|
247 | @request.session[:user_id] = 1 | |
248 | put :update, |
|
248 | put :update, | |
249 | :id => 4, |
|
249 | :id => 4, | |
250 | :default_columns => '1', |
|
250 | :default_columns => '1', | |
251 | :fields => ["status_id", "assigned_to_id"], |
|
251 | :fields => ["status_id", "assigned_to_id"], | |
252 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, |
|
252 | :operators => {"assigned_to_id" => "=", "status_id" => "o"}, | |
253 | :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, |
|
253 | :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, | |
254 | :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} |
|
254 | :query => {"name" => "test_edit_global_public_query", "is_public" => "1"} | |
255 |
|
255 | |||
256 | assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 |
|
256 | assert_redirected_to :controller => 'issues', :action => 'index', :query_id => 4 | |
257 | q = Query.find_by_name('test_edit_global_public_query') |
|
257 | q = Query.find_by_name('test_edit_global_public_query') | |
258 | assert q.is_public? |
|
258 | assert q.is_public? | |
259 | assert q.has_default_columns? |
|
259 | assert q.has_default_columns? | |
260 | assert q.valid? |
|
260 | assert q.valid? | |
261 | end |
|
261 | end | |
262 |
|
262 | |||
263 | def test_update_with_failure |
|
263 | def test_update_with_failure | |
264 | @request.session[:user_id] = 1 |
|
264 | @request.session[:user_id] = 1 | |
265 | put :update, :id => 4, :query => {:name => ''} |
|
265 | put :update, :id => 4, :query => {:name => ''} | |
266 | assert_response :success |
|
266 | assert_response :success | |
267 | assert_template 'edit' |
|
267 | assert_template 'edit' | |
268 | end |
|
268 | end | |
269 |
|
269 | |||
270 | def test_destroy |
|
270 | def test_destroy | |
271 | @request.session[:user_id] = 2 |
|
271 | @request.session[:user_id] = 2 | |
272 | delete :destroy, :id => 1 |
|
272 | delete :destroy, :id => 1 | |
273 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :set_filter => 1, :query_id => nil |
|
273 | assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook', :set_filter => 1, :query_id => nil | |
274 | assert_nil Query.find_by_id(1) |
|
274 | assert_nil Query.find_by_id(1) | |
275 | end |
|
275 | end | |
|
276 | ||||
|
277 | def test_backslash_should_be_escaped_in_filters | |||
|
278 | @request.session[:user_id] = 2 | |||
|
279 | get :new, :subject => 'foo/bar' | |||
|
280 | assert_response :success | |||
|
281 | assert_template 'new' | |||
|
282 | assert_include 'addFilter("subject", "=", ["foo\/bar"]);', response.body | |||
|
283 | end | |||
276 | end |
|
284 | end |
General Comments 0
You need to be logged in to leave comments.
Login now