To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
root / app / helpers / application_helper.rb @ 442:753f1380d6bc
History | View | Annotate | Download (34.8 KB)
| 1 | 37:94944d00e43c | chris | # Redmine - project management software
|
|---|---|---|---|
| 2 | 441:cbce1fd3b1b7 | Chris | # Copyright (C) 2006-2011 Jean-Philippe Lang
|
| 3 | 0:513646585e45 | Chris | #
|
| 4 | # This program is free software; you can redistribute it and/or
|
||
| 5 | # modify it under the terms of the GNU General Public License
|
||
| 6 | # as published by the Free Software Foundation; either version 2
|
||
| 7 | # of the License, or (at your option) any later version.
|
||
| 8 | #
|
||
| 9 | # This program is distributed in the hope that it will be useful,
|
||
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
| 12 | # GNU General Public License for more details.
|
||
| 13 | #
|
||
| 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
|
||
| 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
| 17 | |||
| 18 | require 'forwardable'
|
||
| 19 | require 'cgi'
|
||
| 20 | |||
| 21 | module ApplicationHelper |
||
| 22 | include Redmine::WikiFormatting::Macros::Definitions |
||
| 23 | include Redmine::I18n |
||
| 24 | include GravatarHelper::PublicMethods |
||
| 25 | |||
| 26 | extend Forwardable
|
||
| 27 | def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter |
||
| 28 | |||
| 29 | # Return true if user is authorized for controller/action, otherwise false
|
||
| 30 | def authorize_for(controller, action) |
||
| 31 | User.current.allowed_to?({:controller => controller, :action => action}, @project) |
||
| 32 | end
|
||
| 33 | |||
| 34 | # Display a link if user is authorized
|
||
| 35 | 22:40f7cfd4df19 | chris | #
|
| 36 | # @param [String] name Anchor text (passed to link_to)
|
||
| 37 | 37:94944d00e43c | chris | # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
|
| 38 | 22:40f7cfd4df19 | chris | # @param [optional, Hash] html_options Options passed to link_to
|
| 39 | # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
|
||
| 40 | 0:513646585e45 | Chris | def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference) |
| 41 | 37:94944d00e43c | chris | link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action]) |
| 42 | 0:513646585e45 | Chris | end
|
| 43 | |||
| 44 | # Display a link to remote if user is authorized
|
||
| 45 | def link_to_remote_if_authorized(name, options = {}, html_options = nil) |
||
| 46 | url = options[:url] || {}
|
||
| 47 | link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action]) |
||
| 48 | end
|
||
| 49 | |||
| 50 | # Displays a link to user's account page if active
|
||
| 51 | def link_to_user(user, options={}) |
||
| 52 | if user.is_a?(User) |
||
| 53 | name = h(user.name(options[:format]))
|
||
| 54 | if user.active?
|
||
| 55 | 140:4272e09f4b5f | chris | link_to(name, :controller => 'users', :action => 'show', :id => user) |
| 56 | 0:513646585e45 | Chris | else
|
| 57 | name |
||
| 58 | end
|
||
| 59 | else
|
||
| 60 | h(user.to_s) |
||
| 61 | end
|
||
| 62 | end
|
||
| 63 | |||
| 64 | # Displays a link to +issue+ with its subject.
|
||
| 65 | # Examples:
|
||
| 66 | 441:cbce1fd3b1b7 | Chris | #
|
| 67 | 0:513646585e45 | Chris | # link_to_issue(issue) # => Defect #6: This is the subject
|
| 68 | # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
|
||
| 69 | # link_to_issue(issue, :subject => false) # => Defect #6
|
||
| 70 | # link_to_issue(issue, :project => true) # => Foo - Defect #6
|
||
| 71 | #
|
||
| 72 | def link_to_issue(issue, options={}) |
||
| 73 | title = nil
|
||
| 74 | subject = nil
|
||
| 75 | if options[:subject] == false |
||
| 76 | title = truncate(issue.subject, :length => 60) |
||
| 77 | else
|
||
| 78 | subject = issue.subject |
||
| 79 | if options[:truncate] |
||
| 80 | subject = truncate(subject, :length => options[:truncate]) |
||
| 81 | end
|
||
| 82 | end
|
||
| 83 | 441:cbce1fd3b1b7 | Chris | s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, |
| 84 | 0:513646585e45 | Chris | :class => issue.css_classes,
|
| 85 | :title => title
|
||
| 86 | s << ": #{h subject}" if subject |
||
| 87 | s = "#{h issue.project} - " + s if options[:project] |
||
| 88 | s |
||
| 89 | end
|
||
| 90 | |||
| 91 | # Generates a link to an attachment.
|
||
| 92 | # Options:
|
||
| 93 | # * :text - Link text (default to attachment filename)
|
||
| 94 | # * :download - Force download (default: false)
|
||
| 95 | def link_to_attachment(attachment, options={}) |
||
| 96 | text = options.delete(:text) || attachment.filename
|
||
| 97 | action = options.delete(:download) ? 'download' : 'show' |
||
| 98 | |||
| 99 | link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
|
||
| 100 | end
|
||
| 101 | |||
| 102 | # Generates a link to a SCM revision
|
||
| 103 | # Options:
|
||
| 104 | # * :text - Link text (default to the formatted revision)
|
||
| 105 | def link_to_revision(revision, project, options={}) |
||
| 106 | text = options.delete(:text) || format_revision(revision)
|
||
| 107 | 119:8661b858af72 | Chris | rev = revision.respond_to?(:identifier) ? revision.identifier : revision
|
| 108 | 0:513646585e45 | Chris | |
| 109 | 119:8661b858af72 | Chris | link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
|
| 110 | :title => l(:label_revision_id, format_revision(revision))) |
||
| 111 | 0:513646585e45 | Chris | end
|
| 112 | 441:cbce1fd3b1b7 | Chris | |
| 113 | 210:0579821a129a | Chris | # Generates a link to a message
|
| 114 | def link_to_message(message, options={}, html_options = nil) |
||
| 115 | link_to( |
||
| 116 | h(truncate(message.subject, :length => 60)), |
||
| 117 | { :controller => 'messages', :action => 'show',
|
||
| 118 | :board_id => message.board_id,
|
||
| 119 | :id => message.root,
|
||
| 120 | :r => (message.parent_id && message.id),
|
||
| 121 | :anchor => (message.parent_id ? "message-#{message.id}" : nil) |
||
| 122 | }.merge(options), |
||
| 123 | html_options |
||
| 124 | ) |
||
| 125 | end
|
||
| 126 | 0:513646585e45 | Chris | |
| 127 | 14:1d32c0a0efbf | Chris | # Generates a link to a project if active
|
| 128 | # Examples:
|
||
| 129 | 441:cbce1fd3b1b7 | Chris | #
|
| 130 | 14:1d32c0a0efbf | Chris | # link_to_project(project) # => link to the specified project overview
|
| 131 | # link_to_project(project, :action=>'settings') # => link to project settings
|
||
| 132 | # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
|
||
| 133 | # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
|
||
| 134 | #
|
||
| 135 | def link_to_project(project, options={}, html_options = nil) |
||
| 136 | if project.active?
|
||
| 137 | url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
|
||
| 138 | link_to(h(project), url, html_options) |
||
| 139 | else
|
||
| 140 | h(project) |
||
| 141 | end
|
||
| 142 | end
|
||
| 143 | |||
| 144 | 0:513646585e45 | Chris | def toggle_link(name, id, options={}) |
| 145 | onclick = "Element.toggle('#{id}'); "
|
||
| 146 | onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ") |
||
| 147 | onclick << "return false;"
|
||
| 148 | link_to(name, "#", :onclick => onclick) |
||
| 149 | end
|
||
| 150 | |||
| 151 | def image_to_function(name, function, html_options = {}) |
||
| 152 | html_options.symbolize_keys! |
||
| 153 | tag(:input, html_options.merge({
|
||
| 154 | :type => "image", :src => image_path(name), |
||
| 155 | :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" |
||
| 156 | })) |
||
| 157 | end
|
||
| 158 | |||
| 159 | def prompt_to_remote(name, text, param, url, html_options = {}) |
||
| 160 | html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;" |
||
| 161 | link_to name, {}, html_options
|
||
| 162 | end
|
||
| 163 | 441:cbce1fd3b1b7 | Chris | |
| 164 | 0:513646585e45 | Chris | def format_activity_title(text) |
| 165 | h(truncate_single_line(text, :length => 100)) |
||
| 166 | end
|
||
| 167 | 441:cbce1fd3b1b7 | Chris | |
| 168 | 0:513646585e45 | Chris | def format_activity_day(date) |
| 169 | date == Date.today ? l(:label_today).titleize : format_date(date) |
||
| 170 | end
|
||
| 171 | 441:cbce1fd3b1b7 | Chris | |
| 172 | 0:513646585e45 | Chris | def format_activity_description(text) |
| 173 | h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />") |
||
| 174 | end
|
||
| 175 | |||
| 176 | def format_version_name(version) |
||
| 177 | if version.project == @project |
||
| 178 | h(version) |
||
| 179 | else
|
||
| 180 | h("#{version.project} - #{version}")
|
||
| 181 | end
|
||
| 182 | end
|
||
| 183 | 441:cbce1fd3b1b7 | Chris | |
| 184 | 0:513646585e45 | Chris | def due_date_distance_in_words(date) |
| 185 | if date
|
||
| 186 | l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date)) |
||
| 187 | end
|
||
| 188 | end
|
||
| 189 | |||
| 190 | 441:cbce1fd3b1b7 | Chris | def render_page_hierarchy(pages, node=nil, options={}) |
| 191 | 0:513646585e45 | Chris | content = ''
|
| 192 | if pages[node]
|
||
| 193 | content << "<ul class=\"pages-hierarchy\">\n"
|
||
| 194 | pages[node].each do |page|
|
||
| 195 | content << "<li>"
|
||
| 196 | 37:94944d00e43c | chris | content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
|
| 197 | 441:cbce1fd3b1b7 | Chris | :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil)) |
| 198 | content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id] |
||
| 199 | 0:513646585e45 | Chris | content << "</li>\n"
|
| 200 | end
|
||
| 201 | content << "</ul>\n"
|
||
| 202 | end
|
||
| 203 | content |
||
| 204 | end
|
||
| 205 | 441:cbce1fd3b1b7 | Chris | |
| 206 | 0:513646585e45 | Chris | # Renders flash messages
|
| 207 | def render_flash_messages |
||
| 208 | s = ''
|
||
| 209 | flash.each do |k,v|
|
||
| 210 | s << content_tag('div', v, :class => "flash #{k}") |
||
| 211 | end
|
||
| 212 | s |
||
| 213 | end
|
||
| 214 | 441:cbce1fd3b1b7 | Chris | |
| 215 | 0:513646585e45 | Chris | # Renders tabs and their content
|
| 216 | def render_tabs(tabs) |
||
| 217 | if tabs.any?
|
||
| 218 | render :partial => 'common/tabs', :locals => {:tabs => tabs} |
||
| 219 | else
|
||
| 220 | content_tag 'p', l(:label_no_data), :class => "nodata" |
||
| 221 | end
|
||
| 222 | end
|
||
| 223 | 441:cbce1fd3b1b7 | Chris | |
| 224 | 0:513646585e45 | Chris | # Renders the project quick-jump box
|
| 225 | def render_project_jump_box |
||
| 226 | 441:cbce1fd3b1b7 | Chris | return unless User.current.logged? |
| 227 | projects = User.current.memberships.collect(&:project).compact.uniq |
||
| 228 | 0:513646585e45 | Chris | if projects.any?
|
| 229 | s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
|
||
| 230 | "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
|
||
| 231 | '<option value="" disabled="disabled">---</option>'
|
||
| 232 | s << project_tree_options_for_select(projects, :selected => @project) do |p| |
||
| 233 | { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
|
||
| 234 | end
|
||
| 235 | s << '</select>'
|
||
| 236 | s |
||
| 237 | end
|
||
| 238 | end
|
||
| 239 | 441:cbce1fd3b1b7 | Chris | |
| 240 | 0:513646585e45 | Chris | def project_tree_options_for_select(projects, options = {}) |
| 241 | s = ''
|
||
| 242 | project_tree(projects) do |project, level|
|
||
| 243 | name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '') |
||
| 244 | tag_options = {:value => project.id}
|
||
| 245 | if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project)) |
||
| 246 | tag_options[:selected] = 'selected' |
||
| 247 | else
|
||
| 248 | tag_options[:selected] = nil |
||
| 249 | end
|
||
| 250 | tag_options.merge!(yield(project)) if block_given? |
||
| 251 | s << content_tag('option', name_prefix + h(project), tag_options)
|
||
| 252 | end
|
||
| 253 | s |
||
| 254 | end
|
||
| 255 | 441:cbce1fd3b1b7 | Chris | |
| 256 | 0:513646585e45 | Chris | # Yields the given block for each project with its level in the tree
|
| 257 | 37:94944d00e43c | chris | #
|
| 258 | # Wrapper for Project#project_tree
|
||
| 259 | 0:513646585e45 | Chris | def project_tree(projects, &block) |
| 260 | 37:94944d00e43c | chris | Project.project_tree(projects, &block)
|
| 261 | 0:513646585e45 | Chris | end
|
| 262 | 441:cbce1fd3b1b7 | Chris | |
| 263 | 0:513646585e45 | Chris | def project_nested_ul(projects, &block) |
| 264 | s = ''
|
||
| 265 | if projects.any?
|
||
| 266 | ancestors = [] |
||
| 267 | projects.sort_by(&:lft).each do |project| |
||
| 268 | if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
|
||
| 269 | s << "<ul>\n"
|
||
| 270 | else
|
||
| 271 | ancestors.pop |
||
| 272 | s << "</li>"
|
||
| 273 | 441:cbce1fd3b1b7 | Chris | while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
|
| 274 | 0:513646585e45 | Chris | ancestors.pop |
| 275 | s << "</ul></li>\n"
|
||
| 276 | end
|
||
| 277 | end
|
||
| 278 | s << "<li>"
|
||
| 279 | s << yield(project).to_s
|
||
| 280 | ancestors << project |
||
| 281 | end
|
||
| 282 | s << ("</li></ul>\n" * ancestors.size)
|
||
| 283 | end
|
||
| 284 | s |
||
| 285 | end
|
||
| 286 | 441:cbce1fd3b1b7 | Chris | |
| 287 | 0:513646585e45 | Chris | def principals_check_box_tags(name, principals) |
| 288 | s = ''
|
||
| 289 | principals.sort.each do |principal|
|
||
| 290 | 135:6a2f8e88344e | chris | s << "<label>#{ check_box_tag name, principal.id, false } #{link_to_user principal}</label>\n"
|
| 291 | 0:513646585e45 | Chris | end
|
| 292 | 441:cbce1fd3b1b7 | Chris | s |
| 293 | 0:513646585e45 | Chris | end
|
| 294 | |||
| 295 | # Truncates and returns the string as a single line
|
||
| 296 | def truncate_single_line(string, *args) |
||
| 297 | truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ') |
||
| 298 | end
|
||
| 299 | 441:cbce1fd3b1b7 | Chris | |
| 300 | 0:513646585e45 | Chris | # Truncates at line break after 250 characters or options[:length]
|
| 301 | def truncate_lines(string, options={}) |
||
| 302 | length = options[:length] || 250 |
||
| 303 | if string.to_s =~ /\A(.{#{length}}.*?)$/m |
||
| 304 | "#{$1}..."
|
||
| 305 | else
|
||
| 306 | string |
||
| 307 | end
|
||
| 308 | end
|
||
| 309 | |||
| 310 | def html_hours(text) |
||
| 311 | text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>') |
||
| 312 | end
|
||
| 313 | |||
| 314 | def authoring(created, author, options={}) |
||
| 315 | l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)) |
||
| 316 | end
|
||
| 317 | 441:cbce1fd3b1b7 | Chris | |
| 318 | 0:513646585e45 | Chris | def time_tag(time) |
| 319 | text = distance_of_time_in_words(Time.now, time)
|
||
| 320 | if @project |
||
| 321 | 22:40f7cfd4df19 | chris | link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
|
| 322 | 0:513646585e45 | Chris | else
|
| 323 | content_tag('acronym', text, :title => format_time(time)) |
||
| 324 | end
|
||
| 325 | end
|
||
| 326 | |||
| 327 | def syntax_highlight(name, content) |
||
| 328 | Redmine::SyntaxHighlighting.highlight_by_filename(content, name) |
||
| 329 | end
|
||
| 330 | |||
| 331 | def to_path_param(path) |
||
| 332 | path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
|
||
| 333 | end
|
||
| 334 | |||
| 335 | def pagination_links_full(paginator, count=nil, options={}) |
||
| 336 | page_param = options.delete(:page_param) || :page |
||
| 337 | per_page_links = options.delete(:per_page_links)
|
||
| 338 | url_param = params.dup |
||
| 339 | |||
| 340 | html = ''
|
||
| 341 | if paginator.current.previous
|
||
| 342 | 441:cbce1fd3b1b7 | Chris | html << link_to_content_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' |
| 343 | 0:513646585e45 | Chris | end
|
| 344 | |||
| 345 | html << (pagination_links_each(paginator, options) do |n|
|
||
| 346 | 441:cbce1fd3b1b7 | Chris | link_to_content_update(n.to_s, url_param.merge(page_param => n)) |
| 347 | 0:513646585e45 | Chris | end || '') |
| 348 | 441:cbce1fd3b1b7 | Chris | |
| 349 | 0:513646585e45 | Chris | if paginator.current.next
|
| 350 | 441:cbce1fd3b1b7 | Chris | html << ' ' + link_to_content_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) |
| 351 | 0:513646585e45 | Chris | end
|
| 352 | |||
| 353 | unless count.nil?
|
||
| 354 | html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
|
||
| 355 | if per_page_links != false && links = per_page_links(paginator.items_per_page) |
||
| 356 | html << " | #{links}"
|
||
| 357 | end
|
||
| 358 | end
|
||
| 359 | |||
| 360 | html |
||
| 361 | end
|
||
| 362 | 441:cbce1fd3b1b7 | Chris | |
| 363 | 0:513646585e45 | Chris | def per_page_links(selected=nil) |
| 364 | links = Setting.per_page_options_array.collect do |n| |
||
| 365 | 441:cbce1fd3b1b7 | Chris | n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
|
| 366 | 0:513646585e45 | Chris | end
|
| 367 | links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil |
||
| 368 | end
|
||
| 369 | 441:cbce1fd3b1b7 | Chris | |
| 370 | 0:513646585e45 | Chris | def reorder_links(name, url) |
| 371 | link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) + |
||
| 372 | link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) + |
||
| 373 | link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) + |
||
| 374 | link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest)) |
||
| 375 | end
|
||
| 376 | |||
| 377 | def breadcrumb(*args) |
||
| 378 | elements = args.flatten |
||
| 379 | elements.any? ? content_tag('p', args.join(' » ') + ' » ', :class => 'breadcrumb') : nil |
||
| 380 | end
|
||
| 381 | 441:cbce1fd3b1b7 | Chris | |
| 382 | 0:513646585e45 | Chris | def other_formats_links(&block) |
| 383 | concat('<p class="other-formats">' + l(:label_export_to)) |
||
| 384 | yield Redmine::Views::OtherFormatsBuilder.new(self) |
||
| 385 | concat('</p>')
|
||
| 386 | end
|
||
| 387 | 441:cbce1fd3b1b7 | Chris | |
| 388 | 0:513646585e45 | Chris | def page_header_title |
| 389 | if @project.nil? || @project.new_record? |
||
| 390 | 144:09910262eb0b | luisf | a = [h(Setting.app_title), ''] |
| 391 | |||
| 392 | 0:513646585e45 | Chris | else
|
| 393 | 144:09910262eb0b | luisf | pname = [] |
| 394 | 0:513646585e45 | Chris | b = [] |
| 395 | 441:cbce1fd3b1b7 | Chris | ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
| 396 | 0:513646585e45 | Chris | if ancestors.any?
|
| 397 | root = ancestors.shift |
||
| 398 | 14:1d32c0a0efbf | Chris | b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
|
| 399 | 0:513646585e45 | Chris | if ancestors.size > 2 |
| 400 | 144:09910262eb0b | luisf | b << '…'
|
| 401 | 0:513646585e45 | Chris | ancestors = ancestors[-2, 2] |
| 402 | end
|
||
| 403 | 14:1d32c0a0efbf | Chris | b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
|
| 404 | 144:09910262eb0b | luisf | b = b.join(' » ')
|
| 405 | b << (' »')
|
||
| 406 | 0:513646585e45 | Chris | end
|
| 407 | 144:09910262eb0b | luisf | |
| 408 | pname << h(@project)
|
||
| 409 | |||
| 410 | a = [pname, b] |
||
| 411 | |||
| 412 | 0:513646585e45 | Chris | end
|
| 413 | end
|
||
| 414 | |||
| 415 | def html_title(*args) |
||
| 416 | if args.empty?
|
||
| 417 | title = [] |
||
| 418 | title << @project.name if @project |
||
| 419 | title += @html_title if @html_title |
||
| 420 | title << Setting.app_title
|
||
| 421 | title.select {|t| !t.blank? }.join(' - ')
|
||
| 422 | else
|
||
| 423 | @html_title ||= []
|
||
| 424 | @html_title += args
|
||
| 425 | end
|
||
| 426 | end
|
||
| 427 | |||
| 428 | 14:1d32c0a0efbf | Chris | # Returns the theme, controller name, and action as css classes for the
|
| 429 | # HTML body.
|
||
| 430 | def body_css_classes |
||
| 431 | css = [] |
||
| 432 | if theme = Redmine::Themes.theme(Setting.ui_theme) |
||
| 433 | css << 'theme-' + theme.name
|
||
| 434 | end
|
||
| 435 | |||
| 436 | css << 'controller-' + params[:controller] |
||
| 437 | css << 'action-' + params[:action] |
||
| 438 | css.join(' ')
|
||
| 439 | end
|
||
| 440 | |||
| 441 | 0:513646585e45 | Chris | def accesskey(s) |
| 442 | Redmine::AccessKeys.key_for s |
||
| 443 | end
|
||
| 444 | |||
| 445 | # Formats text according to system settings.
|
||
| 446 | # 2 ways to call this method:
|
||
| 447 | # * with a String: textilizable(text, options)
|
||
| 448 | # * with an object and one of its attribute: textilizable(issue, :description, options)
|
||
| 449 | def textilizable(*args) |
||
| 450 | options = args.last.is_a?(Hash) ? args.pop : {}
|
||
| 451 | case args.size
|
||
| 452 | when 1 |
||
| 453 | obj = options[:object]
|
||
| 454 | text = args.shift |
||
| 455 | when 2 |
||
| 456 | obj = args.shift |
||
| 457 | attr = args.shift |
||
| 458 | text = obj.send(attr).to_s |
||
| 459 | else
|
||
| 460 | raise ArgumentError, 'invalid arguments to textilizable' |
||
| 461 | end
|
||
| 462 | return '' if text.blank? |
||
| 463 | project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
||
| 464 | only_path = options.delete(:only_path) == false ? false : true |
||
| 465 | |||
| 466 | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) } |
||
| 467 | 441:cbce1fd3b1b7 | Chris | |
| 468 | 119:8661b858af72 | Chris | @parsed_headings = []
|
| 469 | text = parse_non_pre_blocks(text) do |text|
|
||
| 470 | 37:94944d00e43c | chris | [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| |
| 471 | 0:513646585e45 | Chris | send method_name, text, project, obj, attr, only_path, options |
| 472 | end
|
||
| 473 | end
|
||
| 474 | 441:cbce1fd3b1b7 | Chris | |
| 475 | 119:8661b858af72 | Chris | if @parsed_headings.any? |
| 476 | replace_toc(text, @parsed_headings)
|
||
| 477 | end
|
||
| 478 | 441:cbce1fd3b1b7 | Chris | |
| 479 | 119:8661b858af72 | Chris | text |
| 480 | 0:513646585e45 | Chris | end
|
| 481 | 441:cbce1fd3b1b7 | Chris | |
| 482 | 0:513646585e45 | Chris | def parse_non_pre_blocks(text) |
| 483 | s = StringScanner.new(text)
|
||
| 484 | tags = [] |
||
| 485 | parsed = ''
|
||
| 486 | while !s.eos?
|
||
| 487 | s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
|
||
| 488 | text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
||
| 489 | if tags.empty?
|
||
| 490 | yield text
|
||
| 491 | end
|
||
| 492 | parsed << text |
||
| 493 | if tag
|
||
| 494 | if closing
|
||
| 495 | if tags.last == tag.downcase
|
||
| 496 | tags.pop |
||
| 497 | end
|
||
| 498 | else
|
||
| 499 | tags << tag.downcase |
||
| 500 | end
|
||
| 501 | parsed << full_tag |
||
| 502 | end
|
||
| 503 | end
|
||
| 504 | # Close any non closing tags
|
||
| 505 | while tag = tags.pop
|
||
| 506 | parsed << "</#{tag}>"
|
||
| 507 | end
|
||
| 508 | parsed |
||
| 509 | end
|
||
| 510 | 441:cbce1fd3b1b7 | Chris | |
| 511 | 0:513646585e45 | Chris | def parse_inline_attachments(text, project, obj, attr, only_path, options) |
| 512 | # when using an image link, try to use an attachment, if possible
|
||
| 513 | if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
||
| 514 | attachments = nil
|
||
| 515 | text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
||
| 516 | 441:cbce1fd3b1b7 | Chris | filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
| 517 | 0:513646585e45 | Chris | attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse |
| 518 | # search for the picture in attachments
|
||
| 519 | if found = attachments.detect { |att| att.filename.downcase == filename }
|
||
| 520 | image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found |
||
| 521 | desc = found.description.to_s.gsub('"', '') |
||
| 522 | if !desc.blank? && alttext.blank?
|
||
| 523 | alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
||
| 524 | end
|
||
| 525 | "src=\"#{image_url}\"#{alt}"
|
||
| 526 | else
|
||
| 527 | m |
||
| 528 | end
|
||
| 529 | end
|
||
| 530 | end
|
||
| 531 | end
|
||
| 532 | |||
| 533 | # Wiki links
|
||
| 534 | #
|
||
| 535 | # Examples:
|
||
| 536 | # [[mypage]]
|
||
| 537 | # [[mypage|mytext]]
|
||
| 538 | # wiki links can refer other project wikis, using project name or identifier:
|
||
| 539 | # [[project:]] -> wiki starting page
|
||
| 540 | # [[project:|mytext]]
|
||
| 541 | # [[project:mypage]]
|
||
| 542 | # [[project:mypage|mytext]]
|
||
| 543 | def parse_wiki_links(text, project, obj, attr, only_path, options) |
||
| 544 | text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| |
||
| 545 | link_project = project |
||
| 546 | esc, all, page, title = $1, $2, $3, $5 |
||
| 547 | if esc.nil?
|
||
| 548 | if page =~ /^([^\:]+)\:(.*)$/ |
||
| 549 | 37:94944d00e43c | chris | link_project = Project.find_by_identifier($1) || Project.find_by_name($1) |
| 550 | 0:513646585e45 | Chris | page = $2
|
| 551 | title ||= $1 if page.blank? |
||
| 552 | end
|
||
| 553 | |||
| 554 | if link_project && link_project.wiki
|
||
| 555 | # extract anchor
|
||
| 556 | anchor = nil
|
||
| 557 | if page =~ /^(.+?)\#(.+)$/ |
||
| 558 | page, anchor = $1, $2 |
||
| 559 | end
|
||
| 560 | # check if page exists
|
||
| 561 | wiki_page = link_project.wiki.find_page(page) |
||
| 562 | url = case options[:wiki_links] |
||
| 563 | when :local; "#{title}.html" |
||
| 564 | when :anchor; "##{title}" # used for single-file wiki export |
||
| 565 | else
|
||
| 566 | 37:94944d00e43c | chris | wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
| 567 | url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) |
||
| 568 | 0:513646585e45 | Chris | end
|
| 569 | link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new'))) |
||
| 570 | else
|
||
| 571 | # project or wiki doesn't exist
|
||
| 572 | all |
||
| 573 | end
|
||
| 574 | else
|
||
| 575 | all |
||
| 576 | end
|
||
| 577 | end
|
||
| 578 | end
|
||
| 579 | 441:cbce1fd3b1b7 | Chris | |
| 580 | 0:513646585e45 | Chris | # Redmine links
|
| 581 | #
|
||
| 582 | # Examples:
|
||
| 583 | # Issues:
|
||
| 584 | # #52 -> Link to issue #52
|
||
| 585 | # Changesets:
|
||
| 586 | # r52 -> Link to revision 52
|
||
| 587 | # commit:a85130f -> Link to scmid starting with a85130f
|
||
| 588 | # Documents:
|
||
| 589 | # document#17 -> Link to document with id 17
|
||
| 590 | # document:Greetings -> Link to the document with title "Greetings"
|
||
| 591 | # document:"Some document" -> Link to the document with title "Some document"
|
||
| 592 | # Versions:
|
||
| 593 | # version#3 -> Link to version with id 3
|
||
| 594 | # version:1.0.0 -> Link to version named "1.0.0"
|
||
| 595 | # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
|
||
| 596 | # Attachments:
|
||
| 597 | # attachment:file.zip -> Link to the attachment of the current object named file.zip
|
||
| 598 | # Source files:
|
||
| 599 | # source:some/file -> Link to the file located at /some/file in the project's repository
|
||
| 600 | # source:some/file@52 -> Link to the file's revision 52
|
||
| 601 | # source:some/file#L120 -> Link to line 120 of the file
|
||
| 602 | # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
|
||
| 603 | # export:some/file -> Force the download of the file
|
||
| 604 | 210:0579821a129a | Chris | # Forum messages:
|
| 605 | 0:513646585e45 | Chris | # message#1218 -> Link to message with id 1218
|
| 606 | 210:0579821a129a | Chris | #
|
| 607 | # Links can refer other objects from other projects, using project identifier:
|
||
| 608 | # identifier:r52
|
||
| 609 | # identifier:document:"Some document"
|
||
| 610 | # identifier:version:1.0.0
|
||
| 611 | # identifier:source:some/file
|
||
| 612 | 0:513646585e45 | Chris | def parse_redmine_links(text, project, obj, attr, only_path, options) |
| 613 | 210:0579821a129a | Chris | text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m| |
| 614 | leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 |
||
| 615 | 0:513646585e45 | Chris | link = nil
|
| 616 | 210:0579821a129a | Chris | if project_identifier
|
| 617 | project = Project.visible.find_by_identifier(project_identifier)
|
||
| 618 | end
|
||
| 619 | 0:513646585e45 | Chris | if esc.nil?
|
| 620 | if prefix.nil? && sep == 'r' |
||
| 621 | 210:0579821a129a | Chris | # project.changesets.visible raises an SQL error because of a double join on repositories
|
| 622 | if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) |
||
| 623 | link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision}, |
||
| 624 | 0:513646585e45 | Chris | :class => 'changeset', |
| 625 | :title => truncate_single_line(changeset.comments, :length => 100)) |
||
| 626 | end
|
||
| 627 | elsif sep == '#' |
||
| 628 | oid = identifier.to_i |
||
| 629 | case prefix
|
||
| 630 | when nil |
||
| 631 | if issue = Issue.visible.find_by_id(oid, :include => :status) |
||
| 632 | link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid}, |
||
| 633 | :class => issue.css_classes,
|
||
| 634 | :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})") |
||
| 635 | end
|
||
| 636 | when 'document' |
||
| 637 | 210:0579821a129a | Chris | if document = Document.visible.find_by_id(oid) |
| 638 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 639 | :class => 'document' |
||
| 640 | end
|
||
| 641 | when 'version' |
||
| 642 | 210:0579821a129a | Chris | if version = Version.visible.find_by_id(oid) |
| 643 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 644 | :class => 'version' |
||
| 645 | end
|
||
| 646 | when 'message' |
||
| 647 | 210:0579821a129a | Chris | if message = Message.visible.find_by_id(oid, :include => :parent) |
| 648 | link = link_to_message(message, {:only_path => only_path}, :class => 'message')
|
||
| 649 | 0:513646585e45 | Chris | end
|
| 650 | when 'project' |
||
| 651 | if p = Project.visible.find_by_id(oid) |
||
| 652 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 653 | 0:513646585e45 | Chris | end
|
| 654 | end
|
||
| 655 | elsif sep == ':' |
||
| 656 | # removes the double quotes if any
|
||
| 657 | name = identifier.gsub(%r{^"(.*)"$}, "\\1") |
||
| 658 | case prefix
|
||
| 659 | when 'document' |
||
| 660 | 210:0579821a129a | Chris | if project && document = project.documents.visible.find_by_title(name)
|
| 661 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 662 | :class => 'document' |
||
| 663 | end
|
||
| 664 | when 'version' |
||
| 665 | 210:0579821a129a | Chris | if project && version = project.versions.visible.find_by_name(name)
|
| 666 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 667 | :class => 'version' |
||
| 668 | end
|
||
| 669 | when 'commit' |
||
| 670 | 210:0579821a129a | Chris | if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) |
| 671 | link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, |
||
| 672 | 0:513646585e45 | Chris | :class => 'changeset', |
| 673 | :title => truncate_single_line(changeset.comments, :length => 100) |
||
| 674 | end
|
||
| 675 | when 'source', 'export' |
||
| 676 | 210:0579821a129a | Chris | if project && project.repository && User.current.allowed_to?(:browse_repository, project) |
| 677 | 0:513646585e45 | Chris | name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
|
| 678 | path, rev, anchor = $1, $3, $5 |
||
| 679 | 210:0579821a129a | Chris | link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, |
| 680 | 0:513646585e45 | Chris | :path => to_path_param(path),
|
| 681 | :rev => rev,
|
||
| 682 | :anchor => anchor,
|
||
| 683 | :format => (prefix == 'export' ? 'raw' : nil)}, |
||
| 684 | :class => (prefix == 'export' ? 'source download' : 'source') |
||
| 685 | end
|
||
| 686 | when 'attachment' |
||
| 687 | attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
||
| 688 | if attachments && attachment = attachments.detect {|a| a.filename == name }
|
||
| 689 | link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
||
| 690 | :class => 'attachment' |
||
| 691 | end
|
||
| 692 | when 'project' |
||
| 693 | if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}]) |
||
| 694 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 695 | 0:513646585e45 | Chris | end
|
| 696 | end
|
||
| 697 | end
|
||
| 698 | end
|
||
| 699 | 210:0579821a129a | Chris | leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
|
| 700 | 0:513646585e45 | Chris | end
|
| 701 | end
|
||
| 702 | 441:cbce1fd3b1b7 | Chris | |
| 703 | 37:94944d00e43c | chris | HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE) |
| 704 | 441:cbce1fd3b1b7 | Chris | |
| 705 | 37:94944d00e43c | chris | # Headings and TOC
|
| 706 | 119:8661b858af72 | Chris | # Adds ids and links to headings unless options[:headings] is set to false
|
| 707 | 37:94944d00e43c | chris | def parse_headings(text, project, obj, attr, only_path, options) |
| 708 | 119:8661b858af72 | Chris | return if options[:headings] == false |
| 709 | 441:cbce1fd3b1b7 | Chris | |
| 710 | 37:94944d00e43c | chris | text.gsub!(HEADING_RE) do |
| 711 | level, attrs, content = $1.to_i, $2, $3 |
||
| 712 | item = strip_tags(content).strip |
||
| 713 | anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-') |
||
| 714 | 119:8661b858af72 | Chris | @parsed_headings << [level, anchor, item]
|
| 715 | 441:cbce1fd3b1b7 | Chris | "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>"
|
| 716 | 119:8661b858af72 | Chris | end
|
| 717 | end
|
||
| 718 | 441:cbce1fd3b1b7 | Chris | |
| 719 | 119:8661b858af72 | Chris | TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE) |
| 720 | 441:cbce1fd3b1b7 | Chris | |
| 721 | 119:8661b858af72 | Chris | # Renders the TOC with given headings
|
| 722 | def replace_toc(text, headings) |
||
| 723 | 37:94944d00e43c | chris | text.gsub!(TOC_RE) do |
| 724 | if headings.empty?
|
||
| 725 | ''
|
||
| 726 | else
|
||
| 727 | div_class = 'toc'
|
||
| 728 | div_class << ' right' if $1 == '>' |
||
| 729 | div_class << ' left' if $1 == '<' |
||
| 730 | out = "<ul class=\"#{div_class}\"><li>"
|
||
| 731 | root = headings.map(&:first).min
|
||
| 732 | current = root |
||
| 733 | started = false
|
||
| 734 | headings.each do |level, anchor, item|
|
||
| 735 | if level > current
|
||
| 736 | out << '<ul><li>' * (level - current)
|
||
| 737 | elsif level < current
|
||
| 738 | out << "</li></ul>\n" * (current - level) + "</li><li>" |
||
| 739 | elsif started
|
||
| 740 | out << '</li><li>'
|
||
| 741 | end
|
||
| 742 | out << "<a href=\"##{anchor}\">#{item}</a>"
|
||
| 743 | current = level |
||
| 744 | started = true
|
||
| 745 | end
|
||
| 746 | out << '</li></ul>' * (current - root)
|
||
| 747 | out << '</li></ul>'
|
||
| 748 | end
|
||
| 749 | end
|
||
| 750 | end
|
||
| 751 | 0:513646585e45 | Chris | |
| 752 | # Same as Rails' simple_format helper without using paragraphs
|
||
| 753 | def simple_format_without_paragraph(text) |
||
| 754 | text.to_s. |
||
| 755 | gsub(/\r\n?/, "\n"). # \r\n and \r -> \n |
||
| 756 | gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br |
||
| 757 | gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br |
||
| 758 | end
|
||
| 759 | |||
| 760 | def lang_options_for_select(blank=true) |
||
| 761 | (blank ? [["(auto)", ""]] : []) + |
||
| 762 | valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
|
||
| 763 | end
|
||
| 764 | |||
| 765 | def label_tag_for(name, option_tags = nil, options = {}) |
||
| 766 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "") |
||
| 767 | content_tag("label", label_text)
|
||
| 768 | end
|
||
| 769 | |||
| 770 | def labelled_tabular_form_for(name, object, options, &proc) |
||
| 771 | options[:html] ||= {}
|
||
| 772 | options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) |
||
| 773 | form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
|
||
| 774 | end
|
||
| 775 | |||
| 776 | def back_url_hidden_field_tag |
||
| 777 | back_url = params[:back_url] || request.env['HTTP_REFERER'] |
||
| 778 | back_url = CGI.unescape(back_url.to_s)
|
||
| 779 | hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank? |
||
| 780 | end
|
||
| 781 | |||
| 782 | def check_all_links(form_name) |
||
| 783 | link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") + |
||
| 784 | " | " +
|
||
| 785 | link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)") |
||
| 786 | end
|
||
| 787 | |||
| 788 | def progress_bar(pcts, options={}) |
||
| 789 | pcts = [pcts, pcts] unless pcts.is_a?(Array) |
||
| 790 | pcts = pcts.collect(&:round)
|
||
| 791 | pcts[1] = pcts[1] - pcts[0] |
||
| 792 | pcts << (100 - pcts[1] - pcts[0]) |
||
| 793 | width = options[:width] || '100px;' |
||
| 794 | legend = options[:legend] || '' |
||
| 795 | content_tag('table',
|
||
| 796 | content_tag('tr',
|
||
| 797 | (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') + |
||
| 798 | (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') + |
||
| 799 | (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '') |
||
| 800 | ), :class => 'progress', :style => "width: #{width};") + |
||
| 801 | content_tag('p', legend, :class => 'pourcent') |
||
| 802 | end
|
||
| 803 | 441:cbce1fd3b1b7 | Chris | |
| 804 | 0:513646585e45 | Chris | def checked_image(checked=true) |
| 805 | if checked
|
||
| 806 | image_tag 'toggle_check.png'
|
||
| 807 | end
|
||
| 808 | end
|
||
| 809 | 441:cbce1fd3b1b7 | Chris | |
| 810 | 0:513646585e45 | Chris | def context_menu(url) |
| 811 | unless @context_menu_included |
||
| 812 | content_for :header_tags do |
||
| 813 | javascript_include_tag('context_menu') +
|
||
| 814 | stylesheet_link_tag('context_menu')
|
||
| 815 | end
|
||
| 816 | 14:1d32c0a0efbf | Chris | if l(:direction) == 'rtl' |
| 817 | content_for :header_tags do |
||
| 818 | stylesheet_link_tag('context_menu_rtl')
|
||
| 819 | end
|
||
| 820 | end
|
||
| 821 | 0:513646585e45 | Chris | @context_menu_included = true |
| 822 | end
|
||
| 823 | javascript_tag "new ContextMenu('#{ url_for(url) }')"
|
||
| 824 | end
|
||
| 825 | |||
| 826 | def context_menu_link(name, url, options={}) |
||
| 827 | options[:class] ||= '' |
||
| 828 | if options.delete(:selected) |
||
| 829 | options[:class] << ' icon-checked disabled' |
||
| 830 | options[:disabled] = true |
||
| 831 | end
|
||
| 832 | if options.delete(:disabled) |
||
| 833 | options.delete(:method)
|
||
| 834 | options.delete(:confirm)
|
||
| 835 | options.delete(:onclick)
|
||
| 836 | options[:class] << ' disabled' |
||
| 837 | url = '#'
|
||
| 838 | end
|
||
| 839 | link_to name, url, options |
||
| 840 | end
|
||
| 841 | |||
| 842 | def calendar_for(field_id) |
||
| 843 | include_calendar_headers_tags |
||
| 844 | image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) + |
||
| 845 | javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
|
||
| 846 | end
|
||
| 847 | |||
| 848 | def include_calendar_headers_tags |
||
| 849 | unless @calendar_headers_tags_included |
||
| 850 | @calendar_headers_tags_included = true |
||
| 851 | content_for :header_tags do |
||
| 852 | start_of_week = case Setting.start_of_week.to_i |
||
| 853 | when 1 |
||
| 854 | 'Calendar._FD = 1;' # Monday |
||
| 855 | when 7 |
||
| 856 | 'Calendar._FD = 0;' # Sunday |
||
| 857 | 441:cbce1fd3b1b7 | Chris | when 6 |
| 858 | 'Calendar._FD = 6;' # Saturday |
||
| 859 | 0:513646585e45 | Chris | else
|
| 860 | '' # use language |
||
| 861 | end
|
||
| 862 | 441:cbce1fd3b1b7 | Chris | |
| 863 | 0:513646585e45 | Chris | javascript_include_tag('calendar/calendar') +
|
| 864 | javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
|
||
| 865 | 441:cbce1fd3b1b7 | Chris | javascript_tag(start_of_week) + |
| 866 | 0:513646585e45 | Chris | javascript_include_tag('calendar/calendar-setup') +
|
| 867 | stylesheet_link_tag('calendar')
|
||
| 868 | end
|
||
| 869 | end
|
||
| 870 | end
|
||
| 871 | |||
| 872 | def content_for(name, content = nil, &block) |
||
| 873 | @has_content ||= {}
|
||
| 874 | @has_content[name] = true |
||
| 875 | super(name, content, &block)
|
||
| 876 | end
|
||
| 877 | |||
| 878 | def has_content?(name) |
||
| 879 | (@has_content && @has_content[name]) || false |
||
| 880 | end
|
||
| 881 | |||
| 882 | # Returns the avatar image tag for the given +user+ if avatars are enabled
|
||
| 883 | # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
|
||
| 884 | def avatar(user, options = { }) |
||
| 885 | if Setting.gravatar_enabled? |
||
| 886 | 22:40f7cfd4df19 | chris | options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
|
| 887 | 0:513646585e45 | Chris | email = nil
|
| 888 | if user.respond_to?(:mail) |
||
| 889 | email = user.mail |
||
| 890 | elsif user.to_s =~ %r{<(.+?)>} |
||
| 891 | email = $1
|
||
| 892 | end
|
||
| 893 | return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil |
||
| 894 | 22:40f7cfd4df19 | chris | else
|
| 895 | ''
|
||
| 896 | 0:513646585e45 | Chris | end
|
| 897 | end
|
||
| 898 | 441:cbce1fd3b1b7 | Chris | |
| 899 | 245:051f544170fe | Chris | # Returns the javascript tags that are included in the html layout head
|
| 900 | def javascript_heads |
||
| 901 | tags = javascript_include_tag(:defaults)
|
||
| 902 | unless User.current.pref.warn_on_leaving_unsaved == '0' |
||
| 903 | tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });") |
||
| 904 | end
|
||
| 905 | tags |
||
| 906 | end
|
||
| 907 | 0:513646585e45 | Chris | |
| 908 | 14:1d32c0a0efbf | Chris | def favicon |
| 909 | "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
|
||
| 910 | end
|
||
| 911 | 441:cbce1fd3b1b7 | Chris | |
| 912 | def robot_exclusion_tag |
||
| 913 | '<meta name="robots" content="noindex,follow,noarchive" />'
|
||
| 914 | end
|
||
| 915 | |||
| 916 | 119:8661b858af72 | Chris | # Returns true if arg is expected in the API response
|
| 917 | def include_in_api_response?(arg) |
||
| 918 | unless @included_in_api_response |
||
| 919 | param = params[:include]
|
||
| 920 | @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',') |
||
| 921 | @included_in_api_response.collect!(&:strip) |
||
| 922 | end
|
||
| 923 | @included_in_api_response.include?(arg.to_s)
|
||
| 924 | end
|
||
| 925 | 14:1d32c0a0efbf | Chris | |
| 926 | 119:8661b858af72 | Chris | # Returns options or nil if nometa param or X-Redmine-Nometa header
|
| 927 | # was set in the request
|
||
| 928 | def api_meta(options) |
||
| 929 | if params[:nometa].present? || request.headers['X-Redmine-Nometa'] |
||
| 930 | # compatibility mode for activeresource clients that raise
|
||
| 931 | # an error when unserializing an array with attributes
|
||
| 932 | nil
|
||
| 933 | else
|
||
| 934 | options |
||
| 935 | end
|
||
| 936 | end
|
||
| 937 | 441:cbce1fd3b1b7 | Chris | |
| 938 | 0:513646585e45 | Chris | private |
| 939 | |||
| 940 | def wiki_helper |
||
| 941 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
||
| 942 | extend helper |
||
| 943 | return self |
||
| 944 | end
|
||
| 945 | 441:cbce1fd3b1b7 | Chris | |
| 946 | def link_to_content_update(text, url_params = {}, html_options = {}) |
||
| 947 | link_to(text, url_params, html_options) |
||
| 948 | 0:513646585e45 | Chris | end
|
| 949 | end |