##// END OF EJS Templates
Adds text formatting to documents index (#202)....
Jean-Philippe Lang -
r3488:4dd43001476b
parent child
Show More
@@ -1,791 +1,801
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
37 end
37 end
38
38
39 # Display a link to remote if user is authorized
39 # Display a link to remote if user is authorized
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
41 url = options[:url] || {}
41 url = options[:url] || {}
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
43 end
43 end
44
44
45 # Displays a link to user's account page if active
45 # Displays a link to user's account page if active
46 def link_to_user(user, options={})
46 def link_to_user(user, options={})
47 if user.is_a?(User)
47 if user.is_a?(User)
48 name = h(user.name(options[:format]))
48 name = h(user.name(options[:format]))
49 if user.active?
49 if user.active?
50 link_to name, :controller => 'users', :action => 'show', :id => user
50 link_to name, :controller => 'users', :action => 'show', :id => user
51 else
51 else
52 name
52 name
53 end
53 end
54 else
54 else
55 h(user.to_s)
55 h(user.to_s)
56 end
56 end
57 end
57 end
58
58
59 # Displays a link to +issue+ with its subject.
59 # Displays a link to +issue+ with its subject.
60 # Examples:
60 # Examples:
61 #
61 #
62 # link_to_issue(issue) # => Defect #6: This is the subject
62 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :subject => false) # => Defect #6
64 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 #
66 #
67 def link_to_issue(issue, options={})
67 def link_to_issue(issue, options={})
68 title = nil
68 title = nil
69 subject = nil
69 subject = nil
70 if options[:subject] == false
70 if options[:subject] == false
71 title = truncate(issue.subject, :length => 60)
71 title = truncate(issue.subject, :length => 60)
72 else
72 else
73 subject = issue.subject
73 subject = issue.subject
74 if options[:truncate]
74 if options[:truncate]
75 subject = truncate(subject, :length => options[:truncate])
75 subject = truncate(subject, :length => options[:truncate])
76 end
76 end
77 end
77 end
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
79 :class => issue.css_classes,
79 :class => issue.css_classes,
80 :title => title
80 :title => title
81 s << ": #{h subject}" if subject
81 s << ": #{h subject}" if subject
82 s = "#{h issue.project} - " + s if options[:project]
82 s = "#{h issue.project} - " + s if options[:project]
83 s
83 s
84 end
84 end
85
85
86 # Generates a link to an attachment.
86 # Generates a link to an attachment.
87 # Options:
87 # Options:
88 # * :text - Link text (default to attachment filename)
88 # * :text - Link text (default to attachment filename)
89 # * :download - Force download (default: false)
89 # * :download - Force download (default: false)
90 def link_to_attachment(attachment, options={})
90 def link_to_attachment(attachment, options={})
91 text = options.delete(:text) || attachment.filename
91 text = options.delete(:text) || attachment.filename
92 action = options.delete(:download) ? 'download' : 'show'
92 action = options.delete(:download) ? 'download' : 'show'
93
93
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
95 end
95 end
96
96
97 # Generates a link to a SCM revision
97 # Generates a link to a SCM revision
98 # Options:
98 # Options:
99 # * :text - Link text (default to the formatted revision)
99 # * :text - Link text (default to the formatted revision)
100 def link_to_revision(revision, project, options={})
100 def link_to_revision(revision, project, options={})
101 text = options.delete(:text) || format_revision(revision)
101 text = options.delete(:text) || format_revision(revision)
102
102
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
104 end
104 end
105
105
106 def toggle_link(name, id, options={})
106 def toggle_link(name, id, options={})
107 onclick = "Element.toggle('#{id}'); "
107 onclick = "Element.toggle('#{id}'); "
108 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
108 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
109 onclick << "return false;"
109 onclick << "return false;"
110 link_to(name, "#", :onclick => onclick)
110 link_to(name, "#", :onclick => onclick)
111 end
111 end
112
112
113 def image_to_function(name, function, html_options = {})
113 def image_to_function(name, function, html_options = {})
114 html_options.symbolize_keys!
114 html_options.symbolize_keys!
115 tag(:input, html_options.merge({
115 tag(:input, html_options.merge({
116 :type => "image", :src => image_path(name),
116 :type => "image", :src => image_path(name),
117 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
117 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
118 }))
118 }))
119 end
119 end
120
120
121 def prompt_to_remote(name, text, param, url, html_options = {})
121 def prompt_to_remote(name, text, param, url, html_options = {})
122 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
122 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
123 link_to name, {}, html_options
123 link_to name, {}, html_options
124 end
124 end
125
125
126 def format_activity_title(text)
126 def format_activity_title(text)
127 h(truncate_single_line(text, :length => 100))
127 h(truncate_single_line(text, :length => 100))
128 end
128 end
129
129
130 def format_activity_day(date)
130 def format_activity_day(date)
131 date == Date.today ? l(:label_today).titleize : format_date(date)
131 date == Date.today ? l(:label_today).titleize : format_date(date)
132 end
132 end
133
133
134 def format_activity_description(text)
134 def format_activity_description(text)
135 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
135 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
136 end
136 end
137
137
138 def format_version_name(version)
138 def format_version_name(version)
139 if version.project == @project
139 if version.project == @project
140 h(version)
140 h(version)
141 else
141 else
142 h("#{version.project} - #{version}")
142 h("#{version.project} - #{version}")
143 end
143 end
144 end
144 end
145
145
146 def due_date_distance_in_words(date)
146 def due_date_distance_in_words(date)
147 if date
147 if date
148 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
148 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
149 end
149 end
150 end
150 end
151
151
152 def render_page_hierarchy(pages, node=nil)
152 def render_page_hierarchy(pages, node=nil)
153 content = ''
153 content = ''
154 if pages[node]
154 if pages[node]
155 content << "<ul class=\"pages-hierarchy\">\n"
155 content << "<ul class=\"pages-hierarchy\">\n"
156 pages[node].each do |page|
156 pages[node].each do |page|
157 content << "<li>"
157 content << "<li>"
158 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
158 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
159 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
159 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
160 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
160 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
161 content << "</li>\n"
161 content << "</li>\n"
162 end
162 end
163 content << "</ul>\n"
163 content << "</ul>\n"
164 end
164 end
165 content
165 content
166 end
166 end
167
167
168 # Renders flash messages
168 # Renders flash messages
169 def render_flash_messages
169 def render_flash_messages
170 s = ''
170 s = ''
171 flash.each do |k,v|
171 flash.each do |k,v|
172 s << content_tag('div', v, :class => "flash #{k}")
172 s << content_tag('div', v, :class => "flash #{k}")
173 end
173 end
174 s
174 s
175 end
175 end
176
176
177 # Renders tabs and their content
177 # Renders tabs and their content
178 def render_tabs(tabs)
178 def render_tabs(tabs)
179 if tabs.any?
179 if tabs.any?
180 render :partial => 'common/tabs', :locals => {:tabs => tabs}
180 render :partial => 'common/tabs', :locals => {:tabs => tabs}
181 else
181 else
182 content_tag 'p', l(:label_no_data), :class => "nodata"
182 content_tag 'p', l(:label_no_data), :class => "nodata"
183 end
183 end
184 end
184 end
185
185
186 # Renders the project quick-jump box
186 # Renders the project quick-jump box
187 def render_project_jump_box
187 def render_project_jump_box
188 # Retrieve them now to avoid a COUNT query
188 # Retrieve them now to avoid a COUNT query
189 projects = User.current.projects.all
189 projects = User.current.projects.all
190 if projects.any?
190 if projects.any?
191 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
191 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
192 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
192 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
193 '<option value="" disabled="disabled">---</option>'
193 '<option value="" disabled="disabled">---</option>'
194 s << project_tree_options_for_select(projects, :selected => @project) do |p|
194 s << project_tree_options_for_select(projects, :selected => @project) do |p|
195 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
195 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
196 end
196 end
197 s << '</select>'
197 s << '</select>'
198 s
198 s
199 end
199 end
200 end
200 end
201
201
202 def project_tree_options_for_select(projects, options = {})
202 def project_tree_options_for_select(projects, options = {})
203 s = ''
203 s = ''
204 project_tree(projects) do |project, level|
204 project_tree(projects) do |project, level|
205 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
205 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
206 tag_options = {:value => project.id}
206 tag_options = {:value => project.id}
207 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
207 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
208 tag_options[:selected] = 'selected'
208 tag_options[:selected] = 'selected'
209 else
209 else
210 tag_options[:selected] = nil
210 tag_options[:selected] = nil
211 end
211 end
212 tag_options.merge!(yield(project)) if block_given?
212 tag_options.merge!(yield(project)) if block_given?
213 s << content_tag('option', name_prefix + h(project), tag_options)
213 s << content_tag('option', name_prefix + h(project), tag_options)
214 end
214 end
215 s
215 s
216 end
216 end
217
217
218 # Yields the given block for each project with its level in the tree
218 # Yields the given block for each project with its level in the tree
219 def project_tree(projects, &block)
219 def project_tree(projects, &block)
220 ancestors = []
220 ancestors = []
221 projects.sort_by(&:lft).each do |project|
221 projects.sort_by(&:lft).each do |project|
222 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
222 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
223 ancestors.pop
223 ancestors.pop
224 end
224 end
225 yield project, ancestors.size
225 yield project, ancestors.size
226 ancestors << project
226 ancestors << project
227 end
227 end
228 end
228 end
229
229
230 def project_nested_ul(projects, &block)
230 def project_nested_ul(projects, &block)
231 s = ''
231 s = ''
232 if projects.any?
232 if projects.any?
233 ancestors = []
233 ancestors = []
234 projects.sort_by(&:lft).each do |project|
234 projects.sort_by(&:lft).each do |project|
235 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
235 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
236 s << "<ul>\n"
236 s << "<ul>\n"
237 else
237 else
238 ancestors.pop
238 ancestors.pop
239 s << "</li>"
239 s << "</li>"
240 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
240 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
241 ancestors.pop
241 ancestors.pop
242 s << "</ul></li>\n"
242 s << "</ul></li>\n"
243 end
243 end
244 end
244 end
245 s << "<li>"
245 s << "<li>"
246 s << yield(project).to_s
246 s << yield(project).to_s
247 ancestors << project
247 ancestors << project
248 end
248 end
249 s << ("</li></ul>\n" * ancestors.size)
249 s << ("</li></ul>\n" * ancestors.size)
250 end
250 end
251 s
251 s
252 end
252 end
253
253
254 def principals_check_box_tags(name, principals)
254 def principals_check_box_tags(name, principals)
255 s = ''
255 s = ''
256 principals.sort.each do |principal|
256 principals.sort.each do |principal|
257 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
257 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
258 end
258 end
259 s
259 s
260 end
260 end
261
261
262 # Truncates and returns the string as a single line
262 # Truncates and returns the string as a single line
263 def truncate_single_line(string, *args)
263 def truncate_single_line(string, *args)
264 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
264 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
265 end
265 end
266
267 # Truncates at line break after 250 characters or options[:length]
268 def truncate_lines(string, options={})
269 length = options[:length] || 250
270 if string.to_s =~ /\A(.{#{length}}.*?)$/m
271 "#{$1}..."
272 else
273 string
274 end
275 end
266
276
267 def html_hours(text)
277 def html_hours(text)
268 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
278 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
269 end
279 end
270
280
271 def authoring(created, author, options={})
281 def authoring(created, author, options={})
272 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
282 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
273 end
283 end
274
284
275 def time_tag(time)
285 def time_tag(time)
276 text = distance_of_time_in_words(Time.now, time)
286 text = distance_of_time_in_words(Time.now, time)
277 if @project
287 if @project
278 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
288 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
279 else
289 else
280 content_tag('acronym', text, :title => format_time(time))
290 content_tag('acronym', text, :title => format_time(time))
281 end
291 end
282 end
292 end
283
293
284 def syntax_highlight(name, content)
294 def syntax_highlight(name, content)
285 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
295 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
286 end
296 end
287
297
288 def to_path_param(path)
298 def to_path_param(path)
289 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
299 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
290 end
300 end
291
301
292 def pagination_links_full(paginator, count=nil, options={})
302 def pagination_links_full(paginator, count=nil, options={})
293 page_param = options.delete(:page_param) || :page
303 page_param = options.delete(:page_param) || :page
294 per_page_links = options.delete(:per_page_links)
304 per_page_links = options.delete(:per_page_links)
295 url_param = params.dup
305 url_param = params.dup
296 # don't reuse query params if filters are present
306 # don't reuse query params if filters are present
297 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
307 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
298
308
299 html = ''
309 html = ''
300 if paginator.current.previous
310 if paginator.current.previous
301 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
311 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
302 end
312 end
303
313
304 html << (pagination_links_each(paginator, options) do |n|
314 html << (pagination_links_each(paginator, options) do |n|
305 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
315 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
306 end || '')
316 end || '')
307
317
308 if paginator.current.next
318 if paginator.current.next
309 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
319 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
310 end
320 end
311
321
312 unless count.nil?
322 unless count.nil?
313 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
323 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
314 if per_page_links != false && links = per_page_links(paginator.items_per_page)
324 if per_page_links != false && links = per_page_links(paginator.items_per_page)
315 html << " | #{links}"
325 html << " | #{links}"
316 end
326 end
317 end
327 end
318
328
319 html
329 html
320 end
330 end
321
331
322 def per_page_links(selected=nil)
332 def per_page_links(selected=nil)
323 url_param = params.dup
333 url_param = params.dup
324 url_param.clear if url_param.has_key?(:set_filter)
334 url_param.clear if url_param.has_key?(:set_filter)
325
335
326 links = Setting.per_page_options_array.collect do |n|
336 links = Setting.per_page_options_array.collect do |n|
327 n == selected ? n : link_to_remote(n, {:update => "content",
337 n == selected ? n : link_to_remote(n, {:update => "content",
328 :url => params.dup.merge(:per_page => n),
338 :url => params.dup.merge(:per_page => n),
329 :method => :get},
339 :method => :get},
330 {:href => url_for(url_param.merge(:per_page => n))})
340 {:href => url_for(url_param.merge(:per_page => n))})
331 end
341 end
332 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
342 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
333 end
343 end
334
344
335 def reorder_links(name, url)
345 def reorder_links(name, url)
336 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
346 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
337 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
347 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
338 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
348 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
339 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
349 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
340 end
350 end
341
351
342 def breadcrumb(*args)
352 def breadcrumb(*args)
343 elements = args.flatten
353 elements = args.flatten
344 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
354 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
345 end
355 end
346
356
347 def other_formats_links(&block)
357 def other_formats_links(&block)
348 concat('<p class="other-formats">' + l(:label_export_to))
358 concat('<p class="other-formats">' + l(:label_export_to))
349 yield Redmine::Views::OtherFormatsBuilder.new(self)
359 yield Redmine::Views::OtherFormatsBuilder.new(self)
350 concat('</p>')
360 concat('</p>')
351 end
361 end
352
362
353 def page_header_title
363 def page_header_title
354 if @project.nil? || @project.new_record?
364 if @project.nil? || @project.new_record?
355 h(Setting.app_title)
365 h(Setting.app_title)
356 else
366 else
357 b = []
367 b = []
358 ancestors = (@project.root? ? [] : @project.ancestors.visible)
368 ancestors = (@project.root? ? [] : @project.ancestors.visible)
359 if ancestors.any?
369 if ancestors.any?
360 root = ancestors.shift
370 root = ancestors.shift
361 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
371 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
362 if ancestors.size > 2
372 if ancestors.size > 2
363 b << '&#8230;'
373 b << '&#8230;'
364 ancestors = ancestors[-2, 2]
374 ancestors = ancestors[-2, 2]
365 end
375 end
366 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
376 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
367 end
377 end
368 b << h(@project)
378 b << h(@project)
369 b.join(' &#187; ')
379 b.join(' &#187; ')
370 end
380 end
371 end
381 end
372
382
373 def html_title(*args)
383 def html_title(*args)
374 if args.empty?
384 if args.empty?
375 title = []
385 title = []
376 title << @project.name if @project
386 title << @project.name if @project
377 title += @html_title if @html_title
387 title += @html_title if @html_title
378 title << Setting.app_title
388 title << Setting.app_title
379 title.select {|t| !t.blank? }.join(' - ')
389 title.select {|t| !t.blank? }.join(' - ')
380 else
390 else
381 @html_title ||= []
391 @html_title ||= []
382 @html_title += args
392 @html_title += args
383 end
393 end
384 end
394 end
385
395
386 def accesskey(s)
396 def accesskey(s)
387 Redmine::AccessKeys.key_for s
397 Redmine::AccessKeys.key_for s
388 end
398 end
389
399
390 # Formats text according to system settings.
400 # Formats text according to system settings.
391 # 2 ways to call this method:
401 # 2 ways to call this method:
392 # * with a String: textilizable(text, options)
402 # * with a String: textilizable(text, options)
393 # * with an object and one of its attribute: textilizable(issue, :description, options)
403 # * with an object and one of its attribute: textilizable(issue, :description, options)
394 def textilizable(*args)
404 def textilizable(*args)
395 options = args.last.is_a?(Hash) ? args.pop : {}
405 options = args.last.is_a?(Hash) ? args.pop : {}
396 case args.size
406 case args.size
397 when 1
407 when 1
398 obj = options[:object]
408 obj = options[:object]
399 text = args.shift
409 text = args.shift
400 when 2
410 when 2
401 obj = args.shift
411 obj = args.shift
402 attr = args.shift
412 attr = args.shift
403 text = obj.send(attr).to_s
413 text = obj.send(attr).to_s
404 else
414 else
405 raise ArgumentError, 'invalid arguments to textilizable'
415 raise ArgumentError, 'invalid arguments to textilizable'
406 end
416 end
407 return '' if text.blank?
417 return '' if text.blank?
408 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
418 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
409 only_path = options.delete(:only_path) == false ? false : true
419 only_path = options.delete(:only_path) == false ? false : true
410
420
411 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
421 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
412
422
413 parse_non_pre_blocks(text) do |text|
423 parse_non_pre_blocks(text) do |text|
414 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
424 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
415 send method_name, text, project, obj, attr, only_path, options
425 send method_name, text, project, obj, attr, only_path, options
416 end
426 end
417 end
427 end
418 end
428 end
419
429
420 def parse_non_pre_blocks(text)
430 def parse_non_pre_blocks(text)
421 s = StringScanner.new(text)
431 s = StringScanner.new(text)
422 tags = []
432 tags = []
423 parsed = ''
433 parsed = ''
424 while !s.eos?
434 while !s.eos?
425 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
435 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
426 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
436 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
427 if tags.empty?
437 if tags.empty?
428 yield text
438 yield text
429 end
439 end
430 parsed << text
440 parsed << text
431 if tag
441 if tag
432 if closing
442 if closing
433 if tags.last == tag.downcase
443 if tags.last == tag.downcase
434 tags.pop
444 tags.pop
435 end
445 end
436 else
446 else
437 tags << tag.downcase
447 tags << tag.downcase
438 end
448 end
439 parsed << full_tag
449 parsed << full_tag
440 end
450 end
441 end
451 end
442 # Close any non closing tags
452 # Close any non closing tags
443 while tag = tags.pop
453 while tag = tags.pop
444 parsed << "</#{tag}>"
454 parsed << "</#{tag}>"
445 end
455 end
446 parsed
456 parsed
447 end
457 end
448
458
449 def parse_inline_attachments(text, project, obj, attr, only_path, options)
459 def parse_inline_attachments(text, project, obj, attr, only_path, options)
450 # when using an image link, try to use an attachment, if possible
460 # when using an image link, try to use an attachment, if possible
451 if options[:attachments] || (obj && obj.respond_to?(:attachments))
461 if options[:attachments] || (obj && obj.respond_to?(:attachments))
452 attachments = nil
462 attachments = nil
453 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
463 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
454 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
464 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
455 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
465 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
456 # search for the picture in attachments
466 # search for the picture in attachments
457 if found = attachments.detect { |att| att.filename.downcase == filename }
467 if found = attachments.detect { |att| att.filename.downcase == filename }
458 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
468 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
459 desc = found.description.to_s.gsub('"', '')
469 desc = found.description.to_s.gsub('"', '')
460 if !desc.blank? && alttext.blank?
470 if !desc.blank? && alttext.blank?
461 alt = " title=\"#{desc}\" alt=\"#{desc}\""
471 alt = " title=\"#{desc}\" alt=\"#{desc}\""
462 end
472 end
463 "src=\"#{image_url}\"#{alt}"
473 "src=\"#{image_url}\"#{alt}"
464 else
474 else
465 m
475 m
466 end
476 end
467 end
477 end
468 end
478 end
469 end
479 end
470
480
471 # Wiki links
481 # Wiki links
472 #
482 #
473 # Examples:
483 # Examples:
474 # [[mypage]]
484 # [[mypage]]
475 # [[mypage|mytext]]
485 # [[mypage|mytext]]
476 # wiki links can refer other project wikis, using project name or identifier:
486 # wiki links can refer other project wikis, using project name or identifier:
477 # [[project:]] -> wiki starting page
487 # [[project:]] -> wiki starting page
478 # [[project:|mytext]]
488 # [[project:|mytext]]
479 # [[project:mypage]]
489 # [[project:mypage]]
480 # [[project:mypage|mytext]]
490 # [[project:mypage|mytext]]
481 def parse_wiki_links(text, project, obj, attr, only_path, options)
491 def parse_wiki_links(text, project, obj, attr, only_path, options)
482 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
492 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
483 link_project = project
493 link_project = project
484 esc, all, page, title = $1, $2, $3, $5
494 esc, all, page, title = $1, $2, $3, $5
485 if esc.nil?
495 if esc.nil?
486 if page =~ /^([^\:]+)\:(.*)$/
496 if page =~ /^([^\:]+)\:(.*)$/
487 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
497 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
488 page = $2
498 page = $2
489 title ||= $1 if page.blank?
499 title ||= $1 if page.blank?
490 end
500 end
491
501
492 if link_project && link_project.wiki
502 if link_project && link_project.wiki
493 # extract anchor
503 # extract anchor
494 anchor = nil
504 anchor = nil
495 if page =~ /^(.+?)\#(.+)$/
505 if page =~ /^(.+?)\#(.+)$/
496 page, anchor = $1, $2
506 page, anchor = $1, $2
497 end
507 end
498 # check if page exists
508 # check if page exists
499 wiki_page = link_project.wiki.find_page(page)
509 wiki_page = link_project.wiki.find_page(page)
500 url = case options[:wiki_links]
510 url = case options[:wiki_links]
501 when :local; "#{title}.html"
511 when :local; "#{title}.html"
502 when :anchor; "##{title}" # used for single-file wiki export
512 when :anchor; "##{title}" # used for single-file wiki export
503 else
513 else
504 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
514 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
505 end
515 end
506 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
516 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
507 else
517 else
508 # project or wiki doesn't exist
518 # project or wiki doesn't exist
509 all
519 all
510 end
520 end
511 else
521 else
512 all
522 all
513 end
523 end
514 end
524 end
515 end
525 end
516
526
517 # Redmine links
527 # Redmine links
518 #
528 #
519 # Examples:
529 # Examples:
520 # Issues:
530 # Issues:
521 # #52 -> Link to issue #52
531 # #52 -> Link to issue #52
522 # Changesets:
532 # Changesets:
523 # r52 -> Link to revision 52
533 # r52 -> Link to revision 52
524 # commit:a85130f -> Link to scmid starting with a85130f
534 # commit:a85130f -> Link to scmid starting with a85130f
525 # Documents:
535 # Documents:
526 # document#17 -> Link to document with id 17
536 # document#17 -> Link to document with id 17
527 # document:Greetings -> Link to the document with title "Greetings"
537 # document:Greetings -> Link to the document with title "Greetings"
528 # document:"Some document" -> Link to the document with title "Some document"
538 # document:"Some document" -> Link to the document with title "Some document"
529 # Versions:
539 # Versions:
530 # version#3 -> Link to version with id 3
540 # version#3 -> Link to version with id 3
531 # version:1.0.0 -> Link to version named "1.0.0"
541 # version:1.0.0 -> Link to version named "1.0.0"
532 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
542 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
533 # Attachments:
543 # Attachments:
534 # attachment:file.zip -> Link to the attachment of the current object named file.zip
544 # attachment:file.zip -> Link to the attachment of the current object named file.zip
535 # Source files:
545 # Source files:
536 # source:some/file -> Link to the file located at /some/file in the project's repository
546 # source:some/file -> Link to the file located at /some/file in the project's repository
537 # source:some/file@52 -> Link to the file's revision 52
547 # source:some/file@52 -> Link to the file's revision 52
538 # source:some/file#L120 -> Link to line 120 of the file
548 # source:some/file#L120 -> Link to line 120 of the file
539 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
549 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
540 # export:some/file -> Force the download of the file
550 # export:some/file -> Force the download of the file
541 # Forum messages:
551 # Forum messages:
542 # message#1218 -> Link to message with id 1218
552 # message#1218 -> Link to message with id 1218
543 def parse_redmine_links(text, project, obj, attr, only_path, options)
553 def parse_redmine_links(text, project, obj, attr, only_path, options)
544 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
554 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
545 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
555 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
546 link = nil
556 link = nil
547 if esc.nil?
557 if esc.nil?
548 if prefix.nil? && sep == 'r'
558 if prefix.nil? && sep == 'r'
549 if project && (changeset = project.changesets.find_by_revision(identifier))
559 if project && (changeset = project.changesets.find_by_revision(identifier))
550 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
560 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
551 :class => 'changeset',
561 :class => 'changeset',
552 :title => truncate_single_line(changeset.comments, :length => 100))
562 :title => truncate_single_line(changeset.comments, :length => 100))
553 end
563 end
554 elsif sep == '#'
564 elsif sep == '#'
555 oid = identifier.to_i
565 oid = identifier.to_i
556 case prefix
566 case prefix
557 when nil
567 when nil
558 if issue = Issue.visible.find_by_id(oid, :include => :status)
568 if issue = Issue.visible.find_by_id(oid, :include => :status)
559 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
569 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
560 :class => issue.css_classes,
570 :class => issue.css_classes,
561 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
571 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
562 end
572 end
563 when 'document'
573 when 'document'
564 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
574 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
565 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
575 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
566 :class => 'document'
576 :class => 'document'
567 end
577 end
568 when 'version'
578 when 'version'
569 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
579 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
570 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
580 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
571 :class => 'version'
581 :class => 'version'
572 end
582 end
573 when 'message'
583 when 'message'
574 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
584 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
575 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
585 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
576 :controller => 'messages',
586 :controller => 'messages',
577 :action => 'show',
587 :action => 'show',
578 :board_id => message.board,
588 :board_id => message.board,
579 :id => message.root,
589 :id => message.root,
580 :anchor => (message.parent ? "message-#{message.id}" : nil)},
590 :anchor => (message.parent ? "message-#{message.id}" : nil)},
581 :class => 'message'
591 :class => 'message'
582 end
592 end
583 when 'project'
593 when 'project'
584 if p = Project.visible.find_by_id(oid)
594 if p = Project.visible.find_by_id(oid)
585 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
595 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
586 :class => 'project'
596 :class => 'project'
587 end
597 end
588 end
598 end
589 elsif sep == ':'
599 elsif sep == ':'
590 # removes the double quotes if any
600 # removes the double quotes if any
591 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
601 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
592 case prefix
602 case prefix
593 when 'document'
603 when 'document'
594 if project && document = project.documents.find_by_title(name)
604 if project && document = project.documents.find_by_title(name)
595 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
596 :class => 'document'
606 :class => 'document'
597 end
607 end
598 when 'version'
608 when 'version'
599 if project && version = project.versions.find_by_name(name)
609 if project && version = project.versions.find_by_name(name)
600 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
601 :class => 'version'
611 :class => 'version'
602 end
612 end
603 when 'commit'
613 when 'commit'
604 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
614 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
605 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
615 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
606 :class => 'changeset',
616 :class => 'changeset',
607 :title => truncate_single_line(changeset.comments, :length => 100)
617 :title => truncate_single_line(changeset.comments, :length => 100)
608 end
618 end
609 when 'source', 'export'
619 when 'source', 'export'
610 if project && project.repository
620 if project && project.repository
611 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
621 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
612 path, rev, anchor = $1, $3, $5
622 path, rev, anchor = $1, $3, $5
613 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
623 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
614 :path => to_path_param(path),
624 :path => to_path_param(path),
615 :rev => rev,
625 :rev => rev,
616 :anchor => anchor,
626 :anchor => anchor,
617 :format => (prefix == 'export' ? 'raw' : nil)},
627 :format => (prefix == 'export' ? 'raw' : nil)},
618 :class => (prefix == 'export' ? 'source download' : 'source')
628 :class => (prefix == 'export' ? 'source download' : 'source')
619 end
629 end
620 when 'attachment'
630 when 'attachment'
621 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
631 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
622 if attachments && attachment = attachments.detect {|a| a.filename == name }
632 if attachments && attachment = attachments.detect {|a| a.filename == name }
623 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
633 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
624 :class => 'attachment'
634 :class => 'attachment'
625 end
635 end
626 when 'project'
636 when 'project'
627 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
637 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
628 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
638 link = link_to h(p.name), {:only_path => only_path, :controller => 'projects', :action => 'show', :id => p},
629 :class => 'project'
639 :class => 'project'
630 end
640 end
631 end
641 end
632 end
642 end
633 end
643 end
634 leading + (link || "#{prefix}#{sep}#{identifier}")
644 leading + (link || "#{prefix}#{sep}#{identifier}")
635 end
645 end
636 end
646 end
637
647
638 # Same as Rails' simple_format helper without using paragraphs
648 # Same as Rails' simple_format helper without using paragraphs
639 def simple_format_without_paragraph(text)
649 def simple_format_without_paragraph(text)
640 text.to_s.
650 text.to_s.
641 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
651 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
642 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
652 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
643 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
653 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
644 end
654 end
645
655
646 def lang_options_for_select(blank=true)
656 def lang_options_for_select(blank=true)
647 (blank ? [["(auto)", ""]] : []) +
657 (blank ? [["(auto)", ""]] : []) +
648 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
658 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
649 end
659 end
650
660
651 def label_tag_for(name, option_tags = nil, options = {})
661 def label_tag_for(name, option_tags = nil, options = {})
652 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
662 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
653 content_tag("label", label_text)
663 content_tag("label", label_text)
654 end
664 end
655
665
656 def labelled_tabular_form_for(name, object, options, &proc)
666 def labelled_tabular_form_for(name, object, options, &proc)
657 options[:html] ||= {}
667 options[:html] ||= {}
658 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
668 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
659 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
669 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
660 end
670 end
661
671
662 def back_url_hidden_field_tag
672 def back_url_hidden_field_tag
663 back_url = params[:back_url] || request.env['HTTP_REFERER']
673 back_url = params[:back_url] || request.env['HTTP_REFERER']
664 back_url = CGI.unescape(back_url.to_s)
674 back_url = CGI.unescape(back_url.to_s)
665 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
675 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
666 end
676 end
667
677
668 def check_all_links(form_name)
678 def check_all_links(form_name)
669 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
679 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
670 " | " +
680 " | " +
671 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
681 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
672 end
682 end
673
683
674 def progress_bar(pcts, options={})
684 def progress_bar(pcts, options={})
675 pcts = [pcts, pcts] unless pcts.is_a?(Array)
685 pcts = [pcts, pcts] unless pcts.is_a?(Array)
676 pcts = pcts.collect(&:round)
686 pcts = pcts.collect(&:round)
677 pcts[1] = pcts[1] - pcts[0]
687 pcts[1] = pcts[1] - pcts[0]
678 pcts << (100 - pcts[1] - pcts[0])
688 pcts << (100 - pcts[1] - pcts[0])
679 width = options[:width] || '100px;'
689 width = options[:width] || '100px;'
680 legend = options[:legend] || ''
690 legend = options[:legend] || ''
681 content_tag('table',
691 content_tag('table',
682 content_tag('tr',
692 content_tag('tr',
683 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
693 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
684 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
694 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
685 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
695 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
686 ), :class => 'progress', :style => "width: #{width};") +
696 ), :class => 'progress', :style => "width: #{width};") +
687 content_tag('p', legend, :class => 'pourcent')
697 content_tag('p', legend, :class => 'pourcent')
688 end
698 end
689
699
690 def checked_image(checked=true)
700 def checked_image(checked=true)
691 if checked
701 if checked
692 image_tag 'toggle_check.png'
702 image_tag 'toggle_check.png'
693 end
703 end
694 end
704 end
695
705
696 def context_menu(url)
706 def context_menu(url)
697 unless @context_menu_included
707 unless @context_menu_included
698 content_for :header_tags do
708 content_for :header_tags do
699 javascript_include_tag('context_menu') +
709 javascript_include_tag('context_menu') +
700 stylesheet_link_tag('context_menu')
710 stylesheet_link_tag('context_menu')
701 end
711 end
702 @context_menu_included = true
712 @context_menu_included = true
703 end
713 end
704 javascript_tag "new ContextMenu('#{ url_for(url) }')"
714 javascript_tag "new ContextMenu('#{ url_for(url) }')"
705 end
715 end
706
716
707 def context_menu_link(name, url, options={})
717 def context_menu_link(name, url, options={})
708 options[:class] ||= ''
718 options[:class] ||= ''
709 if options.delete(:selected)
719 if options.delete(:selected)
710 options[:class] << ' icon-checked disabled'
720 options[:class] << ' icon-checked disabled'
711 options[:disabled] = true
721 options[:disabled] = true
712 end
722 end
713 if options.delete(:disabled)
723 if options.delete(:disabled)
714 options.delete(:method)
724 options.delete(:method)
715 options.delete(:confirm)
725 options.delete(:confirm)
716 options.delete(:onclick)
726 options.delete(:onclick)
717 options[:class] << ' disabled'
727 options[:class] << ' disabled'
718 url = '#'
728 url = '#'
719 end
729 end
720 link_to name, url, options
730 link_to name, url, options
721 end
731 end
722
732
723 def calendar_for(field_id)
733 def calendar_for(field_id)
724 include_calendar_headers_tags
734 include_calendar_headers_tags
725 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
735 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
726 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
736 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
727 end
737 end
728
738
729 def include_calendar_headers_tags
739 def include_calendar_headers_tags
730 unless @calendar_headers_tags_included
740 unless @calendar_headers_tags_included
731 @calendar_headers_tags_included = true
741 @calendar_headers_tags_included = true
732 content_for :header_tags do
742 content_for :header_tags do
733 start_of_week = case Setting.start_of_week.to_i
743 start_of_week = case Setting.start_of_week.to_i
734 when 1
744 when 1
735 'Calendar._FD = 1;' # Monday
745 'Calendar._FD = 1;' # Monday
736 when 7
746 when 7
737 'Calendar._FD = 0;' # Sunday
747 'Calendar._FD = 0;' # Sunday
738 else
748 else
739 '' # use language
749 '' # use language
740 end
750 end
741
751
742 javascript_include_tag('calendar/calendar') +
752 javascript_include_tag('calendar/calendar') +
743 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
753 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
744 javascript_tag(start_of_week) +
754 javascript_tag(start_of_week) +
745 javascript_include_tag('calendar/calendar-setup') +
755 javascript_include_tag('calendar/calendar-setup') +
746 stylesheet_link_tag('calendar')
756 stylesheet_link_tag('calendar')
747 end
757 end
748 end
758 end
749 end
759 end
750
760
751 def content_for(name, content = nil, &block)
761 def content_for(name, content = nil, &block)
752 @has_content ||= {}
762 @has_content ||= {}
753 @has_content[name] = true
763 @has_content[name] = true
754 super(name, content, &block)
764 super(name, content, &block)
755 end
765 end
756
766
757 def has_content?(name)
767 def has_content?(name)
758 (@has_content && @has_content[name]) || false
768 (@has_content && @has_content[name]) || false
759 end
769 end
760
770
761 # Returns the avatar image tag for the given +user+ if avatars are enabled
771 # Returns the avatar image tag for the given +user+ if avatars are enabled
762 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
772 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
763 def avatar(user, options = { })
773 def avatar(user, options = { })
764 if Setting.gravatar_enabled?
774 if Setting.gravatar_enabled?
765 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
775 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
766 email = nil
776 email = nil
767 if user.respond_to?(:mail)
777 if user.respond_to?(:mail)
768 email = user.mail
778 email = user.mail
769 elsif user.to_s =~ %r{<(.+?)>}
779 elsif user.to_s =~ %r{<(.+?)>}
770 email = $1
780 email = $1
771 end
781 end
772 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
782 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
773 end
783 end
774 end
784 end
775
785
776 private
786 private
777
787
778 def wiki_helper
788 def wiki_helper
779 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
789 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
780 extend helper
790 extend helper
781 return self
791 return self
782 end
792 end
783
793
784 def link_to_remote_content_update(text, url_params)
794 def link_to_remote_content_update(text, url_params)
785 link_to_remote(text,
795 link_to_remote(text,
786 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
796 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
787 {:href => url_for(:params => url_params)}
797 {:href => url_for(:params => url_params)}
788 )
798 )
789 end
799 end
790
800
791 end
801 end
@@ -1,3 +1,6
1 <p><%= link_to h(document.title), :controller => 'documents', :action => 'show', :id => document %><br />
1 <h4><%= link_to h(document.title), :controller => 'documents', :action => 'show', :id => document %></h4>
2 <% unless document.description.blank? %><%=h(truncate(document.description, :length => 250)) %><br /><% end %>
2 <p><em><%= format_time(document.updated_on) %></em></p>
3 <em><%= format_time(document.updated_on) %></em></p> No newline at end of file
3
4 <div class="wiki">
5 <%= textilizable(truncate_lines(document.description)) %>
6 </div>
General Comments 0
You need to be logged in to leave comments. Login now