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 / .svn / text-base / application_helper.rb.svn-base @ 441:cbce1fd3b1b7
History | View | Annotate | Download (34.7 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 | link_to name, :controller => 'users', :action => 'show', :id => user |
||
| 56 | 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 | s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
|
||
| 291 | 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 | h(Setting.app_title) |
||
| 391 | else |
||
| 392 | b = [] |
||
| 393 | 441:cbce1fd3b1b7 | Chris | ancestors = (@project.root? ? [] : @project.ancestors.visible.all) |
| 394 | 0:513646585e45 | Chris | if ancestors.any? |
| 395 | root = ancestors.shift |
||
| 396 | 14:1d32c0a0efbf | Chris | b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
|
| 397 | 0:513646585e45 | Chris | if ancestors.size > 2 |
| 398 | b << '…' |
||
| 399 | ancestors = ancestors[-2, 2] |
||
| 400 | end |
||
| 401 | 14:1d32c0a0efbf | Chris | b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
|
| 402 | 0:513646585e45 | Chris | end |
| 403 | b << h(@project) |
||
| 404 | b.join(' » ')
|
||
| 405 | end |
||
| 406 | end |
||
| 407 | |||
| 408 | def html_title(*args) |
||
| 409 | if args.empty? |
||
| 410 | title = [] |
||
| 411 | title << @project.name if @project |
||
| 412 | title += @html_title if @html_title |
||
| 413 | title << Setting.app_title |
||
| 414 | title.select {|t| !t.blank? }.join(' - ')
|
||
| 415 | else |
||
| 416 | @html_title ||= [] |
||
| 417 | @html_title += args |
||
| 418 | end |
||
| 419 | end |
||
| 420 | |||
| 421 | 14:1d32c0a0efbf | Chris | # Returns the theme, controller name, and action as css classes for the |
| 422 | # HTML body. |
||
| 423 | def body_css_classes |
||
| 424 | css = [] |
||
| 425 | if theme = Redmine::Themes.theme(Setting.ui_theme) |
||
| 426 | css << 'theme-' + theme.name |
||
| 427 | end |
||
| 428 | |||
| 429 | css << 'controller-' + params[:controller] |
||
| 430 | css << 'action-' + params[:action] |
||
| 431 | css.join(' ')
|
||
| 432 | end |
||
| 433 | |||
| 434 | 0:513646585e45 | Chris | def accesskey(s) |
| 435 | Redmine::AccessKeys.key_for s |
||
| 436 | end |
||
| 437 | |||
| 438 | # Formats text according to system settings. |
||
| 439 | # 2 ways to call this method: |
||
| 440 | # * with a String: textilizable(text, options) |
||
| 441 | # * with an object and one of its attribute: textilizable(issue, :description, options) |
||
| 442 | def textilizable(*args) |
||
| 443 | options = args.last.is_a?(Hash) ? args.pop : {}
|
||
| 444 | case args.size |
||
| 445 | when 1 |
||
| 446 | obj = options[:object] |
||
| 447 | text = args.shift |
||
| 448 | when 2 |
||
| 449 | obj = args.shift |
||
| 450 | attr = args.shift |
||
| 451 | text = obj.send(attr).to_s |
||
| 452 | else |
||
| 453 | raise ArgumentError, 'invalid arguments to textilizable' |
||
| 454 | end |
||
| 455 | return '' if text.blank? |
||
| 456 | project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil) |
||
| 457 | only_path = options.delete(:only_path) == false ? false : true |
||
| 458 | |||
| 459 | text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
|
||
| 460 | 441:cbce1fd3b1b7 | Chris | |
| 461 | 119:8661b858af72 | Chris | @parsed_headings = [] |
| 462 | text = parse_non_pre_blocks(text) do |text| |
||
| 463 | 37:94944d00e43c | chris | [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_headings].each do |method_name| |
| 464 | 0:513646585e45 | Chris | send method_name, text, project, obj, attr, only_path, options |
| 465 | end |
||
| 466 | end |
||
| 467 | 441:cbce1fd3b1b7 | Chris | |
| 468 | 119:8661b858af72 | Chris | if @parsed_headings.any? |
| 469 | replace_toc(text, @parsed_headings) |
||
| 470 | end |
||
| 471 | 441:cbce1fd3b1b7 | Chris | |
| 472 | 119:8661b858af72 | Chris | text |
| 473 | 0:513646585e45 | Chris | end |
| 474 | 441:cbce1fd3b1b7 | Chris | |
| 475 | 0:513646585e45 | Chris | def parse_non_pre_blocks(text) |
| 476 | s = StringScanner.new(text) |
||
| 477 | tags = [] |
||
| 478 | parsed = '' |
||
| 479 | while !s.eos? |
||
| 480 | s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im) |
||
| 481 | text, full_tag, closing, tag = s[1], s[2], s[3], s[4] |
||
| 482 | if tags.empty? |
||
| 483 | yield text |
||
| 484 | end |
||
| 485 | parsed << text |
||
| 486 | if tag |
||
| 487 | if closing |
||
| 488 | if tags.last == tag.downcase |
||
| 489 | tags.pop |
||
| 490 | end |
||
| 491 | else |
||
| 492 | tags << tag.downcase |
||
| 493 | end |
||
| 494 | parsed << full_tag |
||
| 495 | end |
||
| 496 | end |
||
| 497 | # Close any non closing tags |
||
| 498 | while tag = tags.pop |
||
| 499 | parsed << "</#{tag}>"
|
||
| 500 | end |
||
| 501 | parsed |
||
| 502 | end |
||
| 503 | 441:cbce1fd3b1b7 | Chris | |
| 504 | 0:513646585e45 | Chris | def parse_inline_attachments(text, project, obj, attr, only_path, options) |
| 505 | # when using an image link, try to use an attachment, if possible |
||
| 506 | if options[:attachments] || (obj && obj.respond_to?(:attachments)) |
||
| 507 | attachments = nil |
||
| 508 | text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m| |
||
| 509 | 441:cbce1fd3b1b7 | Chris | filename, ext, alt, alttext = $1.downcase, $2, $3, $4 |
| 510 | 0:513646585e45 | Chris | attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse |
| 511 | # search for the picture in attachments |
||
| 512 | if found = attachments.detect { |att| att.filename.downcase == filename }
|
||
| 513 | image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found |
||
| 514 | desc = found.description.to_s.gsub('"', '')
|
||
| 515 | if !desc.blank? && alttext.blank? |
||
| 516 | alt = " title=\"#{desc}\" alt=\"#{desc}\""
|
||
| 517 | end |
||
| 518 | "src=\"#{image_url}\"#{alt}"
|
||
| 519 | else |
||
| 520 | m |
||
| 521 | end |
||
| 522 | end |
||
| 523 | end |
||
| 524 | end |
||
| 525 | |||
| 526 | # Wiki links |
||
| 527 | # |
||
| 528 | # Examples: |
||
| 529 | # [[mypage]] |
||
| 530 | # [[mypage|mytext]] |
||
| 531 | # wiki links can refer other project wikis, using project name or identifier: |
||
| 532 | # [[project:]] -> wiki starting page |
||
| 533 | # [[project:|mytext]] |
||
| 534 | # [[project:mypage]] |
||
| 535 | # [[project:mypage|mytext]] |
||
| 536 | def parse_wiki_links(text, project, obj, attr, only_path, options) |
||
| 537 | text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m| |
||
| 538 | link_project = project |
||
| 539 | esc, all, page, title = $1, $2, $3, $5 |
||
| 540 | if esc.nil? |
||
| 541 | if page =~ /^([^\:]+)\:(.*)$/ |
||
| 542 | 37:94944d00e43c | chris | link_project = Project.find_by_identifier($1) || Project.find_by_name($1) |
| 543 | 0:513646585e45 | Chris | page = $2 |
| 544 | title ||= $1 if page.blank? |
||
| 545 | end |
||
| 546 | |||
| 547 | if link_project && link_project.wiki |
||
| 548 | # extract anchor |
||
| 549 | anchor = nil |
||
| 550 | if page =~ /^(.+?)\#(.+)$/ |
||
| 551 | page, anchor = $1, $2 |
||
| 552 | end |
||
| 553 | # check if page exists |
||
| 554 | wiki_page = link_project.wiki.find_page(page) |
||
| 555 | url = case options[:wiki_links] |
||
| 556 | when :local; "#{title}.html"
|
||
| 557 | when :anchor; "##{title}" # used for single-file wiki export
|
||
| 558 | else |
||
| 559 | 37:94944d00e43c | chris | wiki_page_id = page.present? ? Wiki.titleize(page) : nil |
| 560 | url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor) |
||
| 561 | 0:513646585e45 | Chris | end |
| 562 | link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
|
||
| 563 | else |
||
| 564 | # project or wiki doesn't exist |
||
| 565 | all |
||
| 566 | end |
||
| 567 | else |
||
| 568 | all |
||
| 569 | end |
||
| 570 | end |
||
| 571 | end |
||
| 572 | 441:cbce1fd3b1b7 | Chris | |
| 573 | 0:513646585e45 | Chris | # Redmine links |
| 574 | # |
||
| 575 | # Examples: |
||
| 576 | # Issues: |
||
| 577 | # #52 -> Link to issue #52 |
||
| 578 | # Changesets: |
||
| 579 | # r52 -> Link to revision 52 |
||
| 580 | # commit:a85130f -> Link to scmid starting with a85130f |
||
| 581 | # Documents: |
||
| 582 | # document#17 -> Link to document with id 17 |
||
| 583 | # document:Greetings -> Link to the document with title "Greetings" |
||
| 584 | # document:"Some document" -> Link to the document with title "Some document" |
||
| 585 | # Versions: |
||
| 586 | # version#3 -> Link to version with id 3 |
||
| 587 | # version:1.0.0 -> Link to version named "1.0.0" |
||
| 588 | # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" |
||
| 589 | # Attachments: |
||
| 590 | # attachment:file.zip -> Link to the attachment of the current object named file.zip |
||
| 591 | # Source files: |
||
| 592 | # source:some/file -> Link to the file located at /some/file in the project's repository |
||
| 593 | # source:some/file@52 -> Link to the file's revision 52 |
||
| 594 | # source:some/file#L120 -> Link to line 120 of the file |
||
| 595 | # source:some/file@52#L120 -> Link to line 120 of the file's revision 52 |
||
| 596 | # export:some/file -> Force the download of the file |
||
| 597 | 210:0579821a129a | Chris | # Forum messages: |
| 598 | 0:513646585e45 | Chris | # message#1218 -> Link to message with id 1218 |
| 599 | 210:0579821a129a | Chris | # |
| 600 | # Links can refer other objects from other projects, using project identifier: |
||
| 601 | # identifier:r52 |
||
| 602 | # identifier:document:"Some document" |
||
| 603 | # identifier:version:1.0.0 |
||
| 604 | # identifier:source:some/file |
||
| 605 | 0:513646585e45 | Chris | def parse_redmine_links(text, project, obj, attr, only_path, options) |
| 606 | 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|
|
| 607 | leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10 |
||
| 608 | 0:513646585e45 | Chris | link = nil |
| 609 | 210:0579821a129a | Chris | if project_identifier |
| 610 | project = Project.visible.find_by_identifier(project_identifier) |
||
| 611 | end |
||
| 612 | 0:513646585e45 | Chris | if esc.nil? |
| 613 | if prefix.nil? && sep == 'r' |
||
| 614 | 210:0579821a129a | Chris | # project.changesets.visible raises an SQL error because of a double join on repositories |
| 615 | if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier)) |
||
| 616 | link = link_to("#{project_prefix}r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
|
||
| 617 | 0:513646585e45 | Chris | :class => 'changeset', |
| 618 | :title => truncate_single_line(changeset.comments, :length => 100)) |
||
| 619 | end |
||
| 620 | elsif sep == '#' |
||
| 621 | oid = identifier.to_i |
||
| 622 | case prefix |
||
| 623 | when nil |
||
| 624 | if issue = Issue.visible.find_by_id(oid, :include => :status) |
||
| 625 | link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
|
||
| 626 | :class => issue.css_classes, |
||
| 627 | :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
|
||
| 628 | end |
||
| 629 | when 'document' |
||
| 630 | 210:0579821a129a | Chris | if document = Document.visible.find_by_id(oid) |
| 631 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 632 | :class => 'document' |
||
| 633 | end |
||
| 634 | when 'version' |
||
| 635 | 210:0579821a129a | Chris | if version = Version.visible.find_by_id(oid) |
| 636 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 637 | :class => 'version' |
||
| 638 | end |
||
| 639 | when 'message' |
||
| 640 | 210:0579821a129a | Chris | if message = Message.visible.find_by_id(oid, :include => :parent) |
| 641 | link = link_to_message(message, {:only_path => only_path}, :class => 'message')
|
||
| 642 | 0:513646585e45 | Chris | end |
| 643 | when 'project' |
||
| 644 | if p = Project.visible.find_by_id(oid) |
||
| 645 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 646 | 0:513646585e45 | Chris | end |
| 647 | end |
||
| 648 | elsif sep == ':' |
||
| 649 | # removes the double quotes if any |
||
| 650 | name = identifier.gsub(%r{^"(.*)"$}, "\\1")
|
||
| 651 | case prefix |
||
| 652 | when 'document' |
||
| 653 | 210:0579821a129a | Chris | if project && document = project.documents.visible.find_by_title(name) |
| 654 | 0:513646585e45 | Chris | link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
|
| 655 | :class => 'document' |
||
| 656 | end |
||
| 657 | when 'version' |
||
| 658 | 210:0579821a129a | Chris | if project && version = project.versions.visible.find_by_name(name) |
| 659 | 0:513646585e45 | Chris | link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
|
| 660 | :class => 'version' |
||
| 661 | end |
||
| 662 | when 'commit' |
||
| 663 | 210:0579821a129a | Chris | if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
|
| 664 | link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
|
||
| 665 | 0:513646585e45 | Chris | :class => 'changeset', |
| 666 | :title => truncate_single_line(changeset.comments, :length => 100) |
||
| 667 | end |
||
| 668 | when 'source', 'export' |
||
| 669 | 210:0579821a129a | Chris | if project && project.repository && User.current.allowed_to?(:browse_repository, project) |
| 670 | 0:513646585e45 | Chris | name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
|
| 671 | path, rev, anchor = $1, $3, $5 |
||
| 672 | 210:0579821a129a | Chris | link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
|
| 673 | 0:513646585e45 | Chris | :path => to_path_param(path), |
| 674 | :rev => rev, |
||
| 675 | :anchor => anchor, |
||
| 676 | :format => (prefix == 'export' ? 'raw' : nil)}, |
||
| 677 | :class => (prefix == 'export' ? 'source download' : 'source') |
||
| 678 | end |
||
| 679 | when 'attachment' |
||
| 680 | attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil) |
||
| 681 | if attachments && attachment = attachments.detect {|a| a.filename == name }
|
||
| 682 | link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
|
||
| 683 | :class => 'attachment' |
||
| 684 | end |
||
| 685 | when 'project' |
||
| 686 | if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
|
||
| 687 | 14:1d32c0a0efbf | Chris | link = link_to_project(p, {:only_path => only_path}, :class => 'project')
|
| 688 | 0:513646585e45 | Chris | end |
| 689 | end |
||
| 690 | end |
||
| 691 | end |
||
| 692 | 210:0579821a129a | Chris | leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")
|
| 693 | 0:513646585e45 | Chris | end |
| 694 | end |
||
| 695 | 441:cbce1fd3b1b7 | Chris | |
| 696 | 37:94944d00e43c | chris | HEADING_RE = /<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>/i unless const_defined?(:HEADING_RE) |
| 697 | 441:cbce1fd3b1b7 | Chris | |
| 698 | 37:94944d00e43c | chris | # Headings and TOC |
| 699 | 119:8661b858af72 | Chris | # Adds ids and links to headings unless options[:headings] is set to false |
| 700 | 37:94944d00e43c | chris | def parse_headings(text, project, obj, attr, only_path, options) |
| 701 | 119:8661b858af72 | Chris | return if options[:headings] == false |
| 702 | 441:cbce1fd3b1b7 | Chris | |
| 703 | 37:94944d00e43c | chris | text.gsub!(HEADING_RE) do |
| 704 | level, attrs, content = $1.to_i, $2, $3 |
||
| 705 | item = strip_tags(content).strip |
||
| 706 | anchor = item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
|
||
| 707 | 119:8661b858af72 | Chris | @parsed_headings << [level, anchor, item] |
| 708 | 441:cbce1fd3b1b7 | Chris | "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">¶</a></h#{level}>"
|
| 709 | 119:8661b858af72 | Chris | end |
| 710 | end |
||
| 711 | 441:cbce1fd3b1b7 | Chris | |
| 712 | 119:8661b858af72 | Chris | TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
|
| 713 | 441:cbce1fd3b1b7 | Chris | |
| 714 | 119:8661b858af72 | Chris | # Renders the TOC with given headings |
| 715 | def replace_toc(text, headings) |
||
| 716 | 37:94944d00e43c | chris | text.gsub!(TOC_RE) do |
| 717 | if headings.empty? |
||
| 718 | '' |
||
| 719 | else |
||
| 720 | div_class = 'toc' |
||
| 721 | div_class << ' right' if $1 == '>' |
||
| 722 | div_class << ' left' if $1 == '<' |
||
| 723 | out = "<ul class=\"#{div_class}\"><li>"
|
||
| 724 | root = headings.map(&:first).min |
||
| 725 | current = root |
||
| 726 | started = false |
||
| 727 | headings.each do |level, anchor, item| |
||
| 728 | if level > current |
||
| 729 | out << '<ul><li>' * (level - current) |
||
| 730 | elsif level < current |
||
| 731 | out << "</li></ul>\n" * (current - level) + "</li><li>" |
||
| 732 | elsif started |
||
| 733 | out << '</li><li>' |
||
| 734 | end |
||
| 735 | out << "<a href=\"##{anchor}\">#{item}</a>"
|
||
| 736 | current = level |
||
| 737 | started = true |
||
| 738 | end |
||
| 739 | out << '</li></ul>' * (current - root) |
||
| 740 | out << '</li></ul>' |
||
| 741 | end |
||
| 742 | end |
||
| 743 | end |
||
| 744 | 0:513646585e45 | Chris | |
| 745 | # Same as Rails' simple_format helper without using paragraphs |
||
| 746 | def simple_format_without_paragraph(text) |
||
| 747 | text.to_s. |
||
| 748 | gsub(/\r\n?/, "\n"). # \r\n and \r -> \n |
||
| 749 | gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br |
||
| 750 | gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br |
||
| 751 | end |
||
| 752 | |||
| 753 | def lang_options_for_select(blank=true) |
||
| 754 | (blank ? [["(auto)", ""]] : []) + |
||
| 755 | valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
|
||
| 756 | end |
||
| 757 | |||
| 758 | def label_tag_for(name, option_tags = nil, options = {})
|
||
| 759 | label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
|
||
| 760 | content_tag("label", label_text)
|
||
| 761 | end |
||
| 762 | |||
| 763 | def labelled_tabular_form_for(name, object, options, &proc) |
||
| 764 | options[:html] ||= {}
|
||
| 765 | options[:html][:class] = 'tabular' unless options[:html].has_key?(:class) |
||
| 766 | form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
|
||
| 767 | end |
||
| 768 | |||
| 769 | def back_url_hidden_field_tag |
||
| 770 | back_url = params[:back_url] || request.env['HTTP_REFERER'] |
||
| 771 | back_url = CGI.unescape(back_url.to_s) |
||
| 772 | hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
|
||
| 773 | end |
||
| 774 | |||
| 775 | def check_all_links(form_name) |
||
| 776 | link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
|
||
| 777 | " | " + |
||
| 778 | link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
|
||
| 779 | end |
||
| 780 | |||
| 781 | def progress_bar(pcts, options={})
|
||
| 782 | pcts = [pcts, pcts] unless pcts.is_a?(Array) |
||
| 783 | pcts = pcts.collect(&:round) |
||
| 784 | pcts[1] = pcts[1] - pcts[0] |
||
| 785 | pcts << (100 - pcts[1] - pcts[0]) |
||
| 786 | width = options[:width] || '100px;' |
||
| 787 | legend = options[:legend] || '' |
||
| 788 | content_tag('table',
|
||
| 789 | content_tag('tr',
|
||
| 790 | (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
|
||
| 791 | (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
|
||
| 792 | (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
|
||
| 793 | ), :class => 'progress', :style => "width: #{width};") +
|
||
| 794 | content_tag('p', legend, :class => 'pourcent')
|
||
| 795 | end |
||
| 796 | 441:cbce1fd3b1b7 | Chris | |
| 797 | 0:513646585e45 | Chris | def checked_image(checked=true) |
| 798 | if checked |
||
| 799 | image_tag 'toggle_check.png' |
||
| 800 | end |
||
| 801 | end |
||
| 802 | 441:cbce1fd3b1b7 | Chris | |
| 803 | 0:513646585e45 | Chris | def context_menu(url) |
| 804 | unless @context_menu_included |
||
| 805 | content_for :header_tags do |
||
| 806 | javascript_include_tag('context_menu') +
|
||
| 807 | stylesheet_link_tag('context_menu')
|
||
| 808 | end |
||
| 809 | 14:1d32c0a0efbf | Chris | if l(:direction) == 'rtl' |
| 810 | content_for :header_tags do |
||
| 811 | stylesheet_link_tag('context_menu_rtl')
|
||
| 812 | end |
||
| 813 | end |
||
| 814 | 0:513646585e45 | Chris | @context_menu_included = true |
| 815 | end |
||
| 816 | javascript_tag "new ContextMenu('#{ url_for(url) }')"
|
||
| 817 | end |
||
| 818 | |||
| 819 | def context_menu_link(name, url, options={})
|
||
| 820 | options[:class] ||= '' |
||
| 821 | if options.delete(:selected) |
||
| 822 | options[:class] << ' icon-checked disabled' |
||
| 823 | options[:disabled] = true |
||
| 824 | end |
||
| 825 | if options.delete(:disabled) |
||
| 826 | options.delete(:method) |
||
| 827 | options.delete(:confirm) |
||
| 828 | options.delete(:onclick) |
||
| 829 | options[:class] << ' disabled' |
||
| 830 | url = '#' |
||
| 831 | end |
||
| 832 | link_to name, url, options |
||
| 833 | end |
||
| 834 | |||
| 835 | def calendar_for(field_id) |
||
| 836 | include_calendar_headers_tags |
||
| 837 | image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
|
||
| 838 | javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
|
||
| 839 | end |
||
| 840 | |||
| 841 | def include_calendar_headers_tags |
||
| 842 | unless @calendar_headers_tags_included |
||
| 843 | @calendar_headers_tags_included = true |
||
| 844 | content_for :header_tags do |
||
| 845 | start_of_week = case Setting.start_of_week.to_i |
||
| 846 | when 1 |
||
| 847 | 'Calendar._FD = 1;' # Monday |
||
| 848 | when 7 |
||
| 849 | 'Calendar._FD = 0;' # Sunday |
||
| 850 | 441:cbce1fd3b1b7 | Chris | when 6 |
| 851 | 'Calendar._FD = 6;' # Saturday |
||
| 852 | 0:513646585e45 | Chris | else |
| 853 | '' # use language |
||
| 854 | end |
||
| 855 | 441:cbce1fd3b1b7 | Chris | |
| 856 | 0:513646585e45 | Chris | javascript_include_tag('calendar/calendar') +
|
| 857 | javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
|
||
| 858 | 441:cbce1fd3b1b7 | Chris | javascript_tag(start_of_week) + |
| 859 | 0:513646585e45 | Chris | javascript_include_tag('calendar/calendar-setup') +
|
| 860 | stylesheet_link_tag('calendar')
|
||
| 861 | end |
||
| 862 | end |
||
| 863 | end |
||
| 864 | |||
| 865 | def content_for(name, content = nil, &block) |
||
| 866 | @has_content ||= {}
|
||
| 867 | @has_content[name] = true |
||
| 868 | super(name, content, &block) |
||
| 869 | end |
||
| 870 | |||
| 871 | def has_content?(name) |
||
| 872 | (@has_content && @has_content[name]) || false |
||
| 873 | end |
||
| 874 | |||
| 875 | # Returns the avatar image tag for the given +user+ if avatars are enabled |
||
| 876 | # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>') |
||
| 877 | def avatar(user, options = { })
|
||
| 878 | if Setting.gravatar_enabled? |
||
| 879 | 22:40f7cfd4df19 | chris | options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
|
| 880 | 0:513646585e45 | Chris | email = nil |
| 881 | if user.respond_to?(:mail) |
||
| 882 | email = user.mail |
||
| 883 | elsif user.to_s =~ %r{<(.+?)>}
|
||
| 884 | email = $1 |
||
| 885 | end |
||
| 886 | return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil |
||
| 887 | 22:40f7cfd4df19 | chris | else |
| 888 | '' |
||
| 889 | 0:513646585e45 | Chris | end |
| 890 | end |
||
| 891 | 441:cbce1fd3b1b7 | Chris | |
| 892 | 245:051f544170fe | Chris | # Returns the javascript tags that are included in the html layout head |
| 893 | def javascript_heads |
||
| 894 | tags = javascript_include_tag(:defaults) |
||
| 895 | unless User.current.pref.warn_on_leaving_unsaved == '0' |
||
| 896 | tags << "\n" + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
|
||
| 897 | end |
||
| 898 | tags |
||
| 899 | end |
||
| 900 | 0:513646585e45 | Chris | |
| 901 | 14:1d32c0a0efbf | Chris | def favicon |
| 902 | "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
|
||
| 903 | end |
||
| 904 | 441:cbce1fd3b1b7 | Chris | |
| 905 | def robot_exclusion_tag |
||
| 906 | '<meta name="robots" content="noindex,follow,noarchive" />' |
||
| 907 | end |
||
| 908 | |||
| 909 | 119:8661b858af72 | Chris | # Returns true if arg is expected in the API response |
| 910 | def include_in_api_response?(arg) |
||
| 911 | unless @included_in_api_response |
||
| 912 | param = params[:include] |
||
| 913 | @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
|
||
| 914 | @included_in_api_response.collect!(&:strip) |
||
| 915 | end |
||
| 916 | @included_in_api_response.include?(arg.to_s) |
||
| 917 | end |
||
| 918 | 14:1d32c0a0efbf | Chris | |
| 919 | 119:8661b858af72 | Chris | # Returns options or nil if nometa param or X-Redmine-Nometa header |
| 920 | # was set in the request |
||
| 921 | def api_meta(options) |
||
| 922 | if params[:nometa].present? || request.headers['X-Redmine-Nometa'] |
||
| 923 | # compatibility mode for activeresource clients that raise |
||
| 924 | # an error when unserializing an array with attributes |
||
| 925 | nil |
||
| 926 | else |
||
| 927 | options |
||
| 928 | end |
||
| 929 | end |
||
| 930 | 441:cbce1fd3b1b7 | Chris | |
| 931 | 0:513646585e45 | Chris | private |
| 932 | |||
| 933 | def wiki_helper |
||
| 934 | helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting) |
||
| 935 | extend helper |
||
| 936 | return self |
||
| 937 | end |
||
| 938 | 441:cbce1fd3b1b7 | Chris | |
| 939 | def link_to_content_update(text, url_params = {}, html_options = {})
|
||
| 940 | link_to(text, url_params, html_options) |
||
| 941 | 0:513646585e45 | Chris | end |
| 942 | end |