##// END OF EJS Templates
The following menus can now be extended by plugins: top_menu, account_menu, application_menu (empty by default)....
Jean-Philippe Lang -
r1123:c5d998528fed
parent child
Show More
@@ -1,450 +1,442
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module ApplicationHelper
19 19 include Redmine::WikiFormatting::Macros::Definitions
20 20
21 21 def current_role
22 22 @current_role ||= User.current.role_for_project(@project)
23 23 end
24 24
25 25 # Return true if user is authorized for controller/action, otherwise false
26 26 def authorize_for(controller, action)
27 27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 28 end
29 29
30 30 # Display a link if user is authorized
31 31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 33 end
34
35 def link_to_signin
36 link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => 'signin'
37 end
38
39 def link_to_signout
40 link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => 'logout'
41 end
42 34
43 35 # Display a link to user's account page
44 36 def link_to_user(user)
45 37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
46 38 end
47 39
48 40 def link_to_issue(issue)
49 41 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
50 42 end
51 43
52 44 def toggle_link(name, id, options={})
53 45 onclick = "Element.toggle('#{id}'); "
54 46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
55 47 onclick << "return false;"
56 48 link_to(name, "#", :onclick => onclick)
57 49 end
58 50
59 51 def show_and_goto_link(name, id, options={})
60 52 onclick = "Element.show('#{id}'); "
61 53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
62 54 onclick << "Element.scrollTo('#{id}'); "
63 55 onclick << "return false;"
64 56 link_to(name, "#", options.merge(:onclick => onclick))
65 57 end
66 58
67 59 def image_to_function(name, function, html_options = {})
68 60 html_options.symbolize_keys!
69 61 tag(:input, html_options.merge({
70 62 :type => "image", :src => image_path(name),
71 63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
72 64 }))
73 65 end
74 66
75 67 def prompt_to_remote(name, text, param, url, html_options = {})
76 68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
77 69 link_to name, {}, html_options
78 70 end
79 71
80 72 def format_date(date)
81 73 return nil unless date
82 74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
83 75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
84 76 date.strftime(@date_format)
85 77 end
86 78
87 79 def format_time(time, include_date = true)
88 80 return nil unless time
89 81 time = time.to_time if time.is_a?(String)
90 82 zone = User.current.time_zone
91 83 if time.utc?
92 84 local = zone ? zone.adjust(time) : time.getlocal
93 85 else
94 86 local = zone ? zone.adjust(time.getutc) : time
95 87 end
96 88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
98 90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
99 91 end
100 92
101 93 def authoring(created, author)
102 94 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
103 95 l(:label_added_time_by, author || 'Anonymous', time_tag)
104 96 end
105 97
106 98 def day_name(day)
107 99 l(:general_day_names).split(',')[day-1]
108 100 end
109 101
110 102 def month_name(month)
111 103 l(:actionview_datehelper_select_month_names).split(',')[month-1]
112 104 end
113 105
114 106 def pagination_links_full(paginator, count=nil, options={})
115 107 page_param = options.delete(:page_param) || :page
116 108 url_param = params.dup
117 109 # don't reuse params if filters are present
118 110 url_param.clear if url_param.has_key?(:set_filter)
119 111
120 112 html = ''
121 113 html << link_to_remote(('&#171; ' + l(:label_previous)),
122 114 {:update => 'content',
123 115 :url => url_param.merge(page_param => paginator.current.previous),
124 116 :complete => 'window.scrollTo(0,0)'},
125 117 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
126 118
127 119 html << (pagination_links_each(paginator, options) do |n|
128 120 link_to_remote(n.to_s,
129 121 {:url => {:params => url_param.merge(page_param => n)},
130 122 :update => 'content',
131 123 :complete => 'window.scrollTo(0,0)'},
132 124 {:href => url_for(:params => url_param.merge(page_param => n))})
133 125 end || '')
134 126
135 127 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
136 128 {:update => 'content',
137 129 :url => url_param.merge(page_param => paginator.current.next),
138 130 :complete => 'window.scrollTo(0,0)'},
139 131 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
140 132
141 133 unless count.nil?
142 134 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
143 135 end
144 136
145 137 html
146 138 end
147 139
148 140 def per_page_links(selected=nil)
149 141 url_param = params.dup
150 142 url_param.clear if url_param.has_key?(:set_filter)
151 143
152 144 links = Setting.per_page_options_array.collect do |n|
153 145 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
154 146 {:href => url_for(url_param.merge(:per_page => n))})
155 147 end
156 148 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
157 149 end
158 150
159 151 def html_title(*args)
160 152 if args.empty?
161 153 title = []
162 154 title << @project.name if @project
163 155 title += @html_title if @html_title
164 156 title << Setting.app_title
165 157 title.compact.join(' - ')
166 158 else
167 159 @html_title ||= []
168 160 @html_title += args
169 161 end
170 162 end
171 163
172 164 def accesskey(s)
173 165 Redmine::AccessKeys.key_for s
174 166 end
175 167
176 168 # Formats text according to system settings.
177 169 # 2 ways to call this method:
178 170 # * with a String: textilizable(text, options)
179 171 # * with an object and one of its attribute: textilizable(issue, :description, options)
180 172 def textilizable(*args)
181 173 options = args.last.is_a?(Hash) ? args.pop : {}
182 174 case args.size
183 175 when 1
184 176 obj = nil
185 177 text = args.shift || ''
186 178 when 2
187 179 obj = args.shift
188 180 text = obj.send(args.shift)
189 181 else
190 182 raise ArgumentError, 'invalid arguments to textilizable'
191 183 end
192 184
193 185 # when using an image link, try to use an attachment, if possible
194 186 attachments = options[:attachments]
195 187 if attachments
196 188 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
197 189 style = $1
198 190 filename = $6
199 191 rf = Regexp.new(filename, Regexp::IGNORECASE)
200 192 # search for the picture in attachments
201 193 if found = attachments.detect { |att| att.filename =~ rf }
202 194 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
203 195 "!#{style}#{image_url}!"
204 196 else
205 197 "!#{style}#{filename}!"
206 198 end
207 199 end
208 200 end
209 201
210 202 text = (Setting.text_formatting == 'textile') ?
211 203 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
212 204 simple_format(auto_link(h(text)))
213 205
214 206 # different methods for formatting wiki links
215 207 case options[:wiki_links]
216 208 when :local
217 209 # used for local links to html files
218 210 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
219 211 when :anchor
220 212 # used for single-file wiki export
221 213 format_wiki_link = Proc.new {|project, title| "##{title}" }
222 214 else
223 215 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
224 216 end
225 217
226 218 project = options[:project] || @project
227 219
228 220 # Wiki links
229 221 #
230 222 # Examples:
231 223 # [[mypage]]
232 224 # [[mypage|mytext]]
233 225 # wiki links can refer other project wikis, using project name or identifier:
234 226 # [[project:]] -> wiki starting page
235 227 # [[project:|mytext]]
236 228 # [[project:mypage]]
237 229 # [[project:mypage|mytext]]
238 230 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
239 231 link_project = project
240 232 esc, all, page, title = $1, $2, $3, $5
241 233 if esc.nil?
242 234 if page =~ /^([^\:]+)\:(.*)$/
243 235 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
244 236 page = $2
245 237 title ||= $1 if page.blank?
246 238 end
247 239
248 240 if link_project && link_project.wiki
249 241 # check if page exists
250 242 wiki_page = link_project.wiki.find_page(page)
251 243 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
252 244 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
253 245 else
254 246 # project or wiki doesn't exist
255 247 title || page
256 248 end
257 249 else
258 250 all
259 251 end
260 252 end
261 253
262 254 # Redmine links
263 255 #
264 256 # Examples:
265 257 # Issues:
266 258 # #52 -> Link to issue #52
267 259 # Changesets:
268 260 # r52 -> Link to revision 52
269 261 # Documents:
270 262 # document#17 -> Link to document with id 17
271 263 # document:Greetings -> Link to the document with title "Greetings"
272 264 # document:"Some document" -> Link to the document with title "Some document"
273 265 # Versions:
274 266 # version#3 -> Link to version with id 3
275 267 # version:1.0.0 -> Link to version named "1.0.0"
276 268 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
277 269 # Attachments:
278 270 # attachment:file.zip -> Link to the attachment of the current object named file.zip
279 271 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
280 272 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
281 273 link = nil
282 274 if esc.nil?
283 275 if prefix.nil? && sep == 'r'
284 276 if project && (changeset = project.changesets.find_by_revision(oid))
285 277 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
286 278 :title => truncate(changeset.comments, 100))
287 279 end
288 280 elsif sep == '#'
289 281 oid = oid.to_i
290 282 case prefix
291 283 when nil
292 284 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
293 285 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
294 286 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
295 287 link = content_tag('del', link) if issue.closed?
296 288 end
297 289 when 'document'
298 290 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
299 291 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
300 292 end
301 293 when 'version'
302 294 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
303 295 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
304 296 end
305 297 end
306 298 elsif sep == ':'
307 299 # removes the double quotes if any
308 300 name = oid.gsub(%r{^"(.*)"$}, "\\1")
309 301 case prefix
310 302 when 'document'
311 303 if project && document = project.documents.find_by_title(name)
312 304 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
313 305 end
314 306 when 'version'
315 307 if project && version = project.versions.find_by_name(name)
316 308 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
317 309 end
318 310 when 'attachment'
319 311 if attachments && attachment = attachments.detect {|a| a.filename == name }
320 312 link = link_to h(attachment.filename), {:controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment'
321 313 end
322 314 end
323 315 end
324 316 end
325 317 leading + (link || "#{prefix}#{sep}#{oid}")
326 318 end
327 319
328 320 text
329 321 end
330 322
331 323 # Same as Rails' simple_format helper without using paragraphs
332 324 def simple_format_without_paragraph(text)
333 325 text.to_s.
334 326 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
335 327 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
336 328 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
337 329 end
338 330
339 331 def error_messages_for(object_name, options = {})
340 332 options = options.symbolize_keys
341 333 object = instance_variable_get("@#{object_name}")
342 334 if object && !object.errors.empty?
343 335 # build full_messages here with controller current language
344 336 full_messages = []
345 337 object.errors.each do |attr, msg|
346 338 next if msg.nil?
347 339 msg = msg.first if msg.is_a? Array
348 340 if attr == "base"
349 341 full_messages << l(msg)
350 342 else
351 343 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
352 344 end
353 345 end
354 346 # retrieve custom values error messages
355 347 if object.errors[:custom_values]
356 348 object.custom_values.each do |v|
357 349 v.errors.each do |attr, msg|
358 350 next if msg.nil?
359 351 msg = msg.first if msg.is_a? Array
360 352 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
361 353 end
362 354 end
363 355 end
364 356 content_tag("div",
365 357 content_tag(
366 358 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
367 359 ) +
368 360 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
369 361 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
370 362 )
371 363 else
372 364 ""
373 365 end
374 366 end
375 367
376 368 def lang_options_for_select(blank=true)
377 369 (blank ? [["(auto)", ""]] : []) +
378 370 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
379 371 end
380 372
381 373 def label_tag_for(name, option_tags = nil, options = {})
382 374 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
383 375 content_tag("label", label_text)
384 376 end
385 377
386 378 def labelled_tabular_form_for(name, object, options, &proc)
387 379 options[:html] ||= {}
388 380 options[:html].store :class, "tabular"
389 381 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
390 382 end
391 383
392 384 def check_all_links(form_name)
393 385 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
394 386 " | " +
395 387 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
396 388 end
397 389
398 390 def progress_bar(pcts, options={})
399 391 pcts = [pcts, pcts] unless pcts.is_a?(Array)
400 392 pcts[1] = pcts[1] - pcts[0]
401 393 pcts << (100 - pcts[1] - pcts[0])
402 394 width = options[:width] || '100px;'
403 395 legend = options[:legend] || ''
404 396 content_tag('table',
405 397 content_tag('tr',
406 398 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
407 399 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
408 400 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
409 401 ), :class => 'progress', :style => "width: #{width};") +
410 402 content_tag('p', legend, :class => 'pourcent')
411 403 end
412 404
413 405 def context_menu_link(name, url, options={})
414 406 options[:class] ||= ''
415 407 if options.delete(:selected)
416 408 options[:class] << ' icon-checked disabled'
417 409 options[:disabled] = true
418 410 end
419 411 if options.delete(:disabled)
420 412 options.delete(:method)
421 413 options.delete(:confirm)
422 414 options.delete(:onclick)
423 415 options[:class] << ' disabled'
424 416 url = '#'
425 417 end
426 418 link_to name, url, options
427 419 end
428 420
429 421 def calendar_for(field_id)
430 422 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
431 423 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
432 424 end
433 425
434 426 def wikitoolbar_for(field_id)
435 427 return '' unless Setting.text_formatting == 'textile'
436 428 javascript_include_tag('jstoolbar/jstoolbar') +
437 429 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
438 430 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
439 431 end
440 432
441 433 def content_for(name, content = nil, &block)
442 434 @has_content ||= {}
443 435 @has_content[name] = true
444 436 super(name, content, &block)
445 437 end
446 438
447 439 def has_content?(name)
448 440 (@has_content && @has_content[name]) || false
449 441 end
450 442 end
@@ -1,75 +1,65
1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 3 <head>
4 4 <title><%=h html_title %></title>
5 5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6 6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
7 7 <meta name="keywords" content="issue,bug,tracker" />
8 8 <%= stylesheet_link_tag 'application', :media => 'all' %>
9 9 <%= javascript_include_tag :defaults %>
10 10 <%= stylesheet_link_tag 'jstoolbar' %>
11 11 <!--[if IE]>
12 12 <style type="text/css">
13 13 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
14 14 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
15 15 </style>
16 16 <![endif]-->
17 17
18 18 <!-- page specific tags --><%= yield :header_tags %>
19 19 </head>
20 20 <body>
21 21 <div id="wrapper">
22 22 <div id="top-menu">
23 23 <div id="account">
24 <% if User.current.logged? %>
25 <%=l(:label_logged_as)%> <%= User.current.login %> -
26 <%= link_to l(:label_my_account), { :controller => 'my', :action => 'account' }, :class => 'myaccount' %>
27 <%= link_to_signout %>
28 <% else %>
29 <%= link_to_signin %>
30 <%= link_to(l(:label_register), { :controller => 'account',:action => 'register' }, :class => 'register') if Setting.self_registration? %>
31 <% end %>
24 <%= render_menu :account_menu -%>
32 25 </div>
33 <%= link_to l(:label_home), home_url, :class => 'home' %>
34 <%= link_to l(:label_my_page), { :controller => 'my', :action => 'page'}, :class => 'mypage' if User.current.logged? %>
35 <%= link_to l(:label_project_plural), { :controller => 'projects' }, :class => 'projects' %>
36 <%= link_to l(:label_administration), { :controller => 'admin' }, :class => 'admin' if User.current.admin? %>
37 <%= link_to l(:label_help), Redmine::Info.help_url, :class => 'help' %>
26 <%= content_tag('div', "#{l(:label_logged_as)} #{User.current.login}", :id => 'loggedas') if User.current.logged? %>
27 <%= render_menu :top_menu -%>
38 28 </div>
39 29
40 30 <div id="header">
41 31 <div id="quick-search">
42 32 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
43 33 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
44 34 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
45 35 <% end %>
46 36 <%= render :partial => 'layouts/project_selector' if User.current.memberships.any? %>
47 37 </div>
48 38
49 39 <h1><%= h(@project ? @project.name : Setting.app_title) %></h1>
50 40
51 41 <div id="main-menu">
52 42 <%= render_main_menu(@project) %>
53 43 </div>
54 44 </div>
55 45
56 46 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
57 47 <div id="sidebar">
58 48 <%= yield :sidebar %>
59 49 </div>
60 50
61 51 <div id="content">
62 52 <%= content_tag('div', flash[:error], :class => 'flash error') if flash[:error] %>
63 53 <%= content_tag('div', flash[:notice], :class => 'flash notice') if flash[:notice] %>
64 54 <%= yield %>
65 55 </div>
66 56 </div>
67 57
68 58 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
69 59
70 60 <div id="footer">
71 61 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> <%= Redmine::VERSION %> &copy; 2006-2008 Jean-Philippe Lang
72 62 </div>
73 63 </div>
74 64 </body>
75 65 </html>
@@ -1,38 +1,40
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 map.home '', :controller => 'welcome'
10
10 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signout 'logout', :controller => 'account', :action => 'logout'
12
11 13 map.connect 'wiki/:id/:page/:action', :controller => 'wiki', :page => nil
12 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
13 15 map.connect 'help/:ctrl/:page', :controller => 'help'
14 16 #map.connect ':controller/:action/:id/:sort_key/:sort_order'
15 17
16 18 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
17 19 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
18 20 map.connect 'projects/:project_id/news/:action', :controller => 'news'
19 21 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
20 22 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
21 23 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
22 24
23 25 map.with_options :controller => 'repositories' do |omap|
24 26 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
25 27 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
26 28 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
27 29 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
28 30 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
29 31 end
30 32
31 33 # Allow downloading Web Service WSDL as a file with an extension
32 34 # instead of a file named 'wsdl'
33 35 map.connect ':controller/service.wsdl', :action => 'wsdl'
34 36
35 37
36 38 # Install the default route as the lowest priority.
37 39 map.connect ':controller/:action/:id'
38 40 end
@@ -1,110 +1,128
1 1 require 'redmine/access_control'
2 2 require 'redmine/menu_manager'
3 3 require 'redmine/mime_type'
4 4 require 'redmine/themes'
5 5 require 'redmine/plugin'
6 6
7 7 begin
8 8 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
9 9 rescue LoadError
10 10 # RMagick is not available
11 11 end
12 12
13 13 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
14 14
15 15 # Permissions
16 16 Redmine::AccessControl.map do |map|
17 17 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
18 18 map.permission :search_project, {:search => :index}, :public => true
19 19 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
20 20 map.permission :select_project_modules, {:projects => :modules}, :require => :member
21 21 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member
22 22 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
23 23
24 24 map.project_module :issue_tracking do |map|
25 25 # Issue categories
26 26 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
27 27 # Issues
28 28 map.permission :view_issues, {:projects => [:changelog, :roadmap],
29 29 :issues => [:index, :changes, :show, :context_menu],
30 30 :versions => [:show, :status_by],
31 31 :queries => :index,
32 32 :reports => :issue_report}, :public => true
33 33 map.permission :add_issues, {:issues => :new}
34 34 map.permission :edit_issues, {:issues => [:edit, :bulk_edit, :destroy_attachment]}
35 35 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
36 36 map.permission :add_issue_notes, {:issues => :edit}
37 37 map.permission :move_issues, {:issues => :move}, :require => :loggedin
38 38 map.permission :delete_issues, {:issues => :destroy}, :require => :member
39 39 # Queries
40 40 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
41 41 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
42 42 # Gantt & calendar
43 43 map.permission :view_gantt, :projects => :gantt
44 44 map.permission :view_calendar, :projects => :calendar
45 45 end
46 46
47 47 map.project_module :time_tracking do |map|
48 48 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
49 49 map.permission :view_time_entries, :timelog => [:details, :report]
50 50 end
51 51
52 52 map.project_module :news do |map|
53 53 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
54 54 map.permission :view_news, {:news => [:index, :show]}, :public => true
55 55 map.permission :comment_news, {:news => :add_comment}
56 56 end
57 57
58 58 map.project_module :documents do |map|
59 59 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment, :destroy_attachment]}, :require => :loggedin
60 60 map.permission :view_documents, :documents => [:index, :show, :download]
61 61 end
62 62
63 63 map.project_module :files do |map|
64 64 map.permission :manage_files, {:projects => :add_file, :versions => :destroy_file}, :require => :loggedin
65 65 map.permission :view_files, :projects => :list_files, :versions => :download
66 66 end
67 67
68 68 map.project_module :wiki do |map|
69 69 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
70 70 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
71 71 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
72 72 map.permission :view_wiki_pages, :wiki => [:index, :history, :diff, :annotate, :special]
73 73 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment, :destroy_attachment]
74 74 end
75 75
76 76 map.project_module :repository do |map|
77 77 map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
78 78 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
79 79 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
80 80 end
81 81
82 82 map.project_module :boards do |map|
83 83 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
84 84 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
85 85 map.permission :add_messages, {:messages => [:new, :reply]}
86 86 map.permission :edit_messages, {:messages => :edit}, :require => :member
87 87 map.permission :delete_messages, {:messages => :destroy}, :require => :member
88 88 end
89 89 end
90 90
91 # Project menu configuration
91 Redmine::MenuManager.map :top_menu do |menu|
92 menu.push :home, :home_url, :html => { :class => 'home' }
93 menu.push :my_page, { :controller => 'my', :action => 'page' }, :html => { :class => 'mypage' }, :if => Proc.new { User.current.logged? }
94 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural, :html => { :class => 'projects' }
95 menu.push :administration, { :controller => 'admin', :action => 'index' }, :html => { :class => 'admin' }, :if => Proc.new { User.current.admin? }
96 menu.push :help, Redmine::Info.help_url, :html => { :class => 'help' }
97 end
98
99 Redmine::MenuManager.map :account_menu do |menu|
100 menu.push :login, :signin_url, :html => { :class => 'login' }, :if => Proc.new { !User.current.logged? }
101 menu.push :register, { :controller => 'account', :action => 'register' }, :html => { :class => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
102 menu.push :my_account, { :controller => 'my', :action => 'account' }, :html => { :class => 'myaccount' }, :if => Proc.new { User.current.logged? }
103 menu.push :logout, :signout_url, :html => { :class => 'logout' }, :if => Proc.new { User.current.logged? }
104 end
105
106 Redmine::MenuManager.map :application_menu do |menu|
107 # Empty
108 end
109
92 110 Redmine::MenuManager.map :project_menu do |menu|
93 menu.push :overview, { :controller => 'projects', :action => 'show' }, :caption => :label_overview
94 menu.push :activity, { :controller => 'projects', :action => 'activity' }, :caption => :label_activity
111 menu.push :overview, { :controller => 'projects', :action => 'show' }
112 menu.push :activity, { :controller => 'projects', :action => 'activity' }
95 113 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
96 :if => Proc.new { |p| p.versions.any? }, :caption => :label_roadmap
114 :if => Proc.new { |p| p.versions.any? }
97 115 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
98 116 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
99 117 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
100 118 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
101 119 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
102 120 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
103 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }, :caption => :label_wiki
121 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
104 122 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
105 123 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
106 124 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
107 125 menu.push :repository, { :controller => 'repositories', :action => 'show' },
108 :if => Proc.new { |p| p.repository && !p.repository.new_record? }, :caption => :label_repository
109 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :caption => :label_settings
126 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
127 menu.push :settings, { :controller => 'projects', :action => 'settings' }
110 128 end
@@ -1,125 +1,139
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module MenuManager
20 20 module MenuController
21 21 def self.included(base)
22 22 base.extend(ClassMethods)
23 23 end
24 24
25 25 module ClassMethods
26 26 @@menu_items = Hash.new {|hash, key| hash[key] = {:default => key, :actions => {}}}
27 27 mattr_accessor :menu_items
28 28
29 29 # Set the menu item name for a controller or specific actions
30 30 # Examples:
31 31 # * menu_item :tickets # => sets the menu name to :tickets for the whole controller
32 32 # * menu_item :tickets, :only => :list # => sets the menu name to :tickets for the 'list' action only
33 33 # * menu_item :tickets, :only => [:list, :show] # => sets the menu name to :tickets for 2 actions only
34 34 #
35 35 # The default menu item name for a controller is controller_name by default
36 36 # Eg. the default menu item name for ProjectsController is :projects
37 37 def menu_item(id, options = {})
38 38 if actions = options[:only]
39 39 actions = [] << actions unless actions.is_a?(Array)
40 40 actions.each {|a| menu_items[controller_name.to_sym][:actions][a.to_sym] = id}
41 41 else
42 42 menu_items[controller_name.to_sym][:default] = id
43 43 end
44 44 end
45 45 end
46 46
47 47 def menu_items
48 48 self.class.menu_items
49 49 end
50 50
51 51 # Returns the menu item name according to the current action
52 52 def current_menu_item
53 53 menu_items[controller_name.to_sym][:actions][action_name.to_sym] ||
54 54 menu_items[controller_name.to_sym][:default]
55 55 end
56 56 end
57 57
58 58 module MenuHelper
59 59 # Returns the current menu item name
60 60 def current_menu_item
61 61 @controller.current_menu_item
62 62 end
63 63
64 # Renders the application main menu as a ul element
64 # Renders the application main menu
65 65 def render_main_menu(project)
66 render_menu((project && !project.new_record?) ? :project_menu : :application_menu, project)
67 end
68
69 def render_menu(menu, project=nil)
66 70 links = []
67 Redmine::MenuManager.allowed_items(:project_menu, User.current, project).each do |item|
71 Redmine::MenuManager.allowed_items(menu, User.current, project).each do |item|
68 72 unless item.condition && !item.condition.call(project)
73 url = case item.url
74 when Hash
75 project.nil? ? item.url : {item.param => project}.merge(item.url)
76 when Symbol
77 send(item.url)
78 else
79 item.url
80 end
81 #url = (project && item.url.is_a?(Hash)) ? {item.param => project}.merge(item.url) : (item.url.is_a?(Symbol) ? send(item.url) : item.url)
69 82 links << content_tag('li',
70 link_to(l(item.caption), {item.param => project}.merge(item.url),
71 (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
83 link_to(l(item.caption), url, (current_menu_item == item.name ? item.html_options.merge(:class => 'selected') : item.html_options)))
72 84 end
73 end if project && !project.new_record?
85 end
74 86 links.empty? ? nil : content_tag('ul', links.join("\n"))
75 87 end
76 88 end
77 89
78 90 class << self
79 91 def map(menu_name)
80 92 mapper = Mapper.new
81 93 yield mapper
82 94 @items ||= {}
83 95 @items[menu_name.to_sym] ||= []
84 96 @items[menu_name.to_sym] += mapper.items
85 97 end
86 98
87 99 def items(menu_name)
88 100 @items[menu_name.to_sym] || []
89 101 end
90 102
91 103 def allowed_items(menu_name, user, project)
92 items(menu_name).select {|item| user && user.allowed_to?(item.url, project)}
104 project ? items(menu_name).select {|item| user && user.allowed_to?(item.url, project)} : items(menu_name)
93 105 end
94 106 end
95 107
96 108 class Mapper
97 109 # Adds an item at the end of the menu. Available options:
98 110 # * param: the parameter name that is used for the project id (default is :id)
99 # * condition: a proc that is called before rendering the item, the item is displayed only if it returns true
111 # * if: a proc that is called before rendering the item, the item is displayed only if it returns true
100 112 # * caption: the localized string key that is used as the item label
101 113 # * html_options: a hash of html options that are passed to link_to
102 114 def push(name, url, options={})
103 @items ||= []
104 @items << MenuItem.new(name, url, options)
115 items << MenuItem.new(name, url, options)
105 116 end
106 117
107 118 def items
108 @items
119 @items ||= []
109 120 end
110 121 end
111 122
112 123 class MenuItem
124 include GLoc
113 125 attr_reader :name, :url, :param, :condition, :caption, :html_options
114 126
115 127 def initialize(name, url, options)
128 raise "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
129 raise "Invalid option :html for menu item '#{name}'" if options[:html] && !options[:html].is_a?(Hash)
116 130 @name = name
117 131 @url = url
118 132 @condition = options[:if]
119 133 @param = options[:param] || :id
120 @caption = options[:caption] || name.to_s.humanize
134 @caption = options[:caption] || (l_has_string?("label_#{name}".to_sym) ? "label_#{name}".to_sym : name.to_s.humanize)
121 135 @html_options = options[:html] || {}
122 136 end
123 137 end
124 138 end
125 139 end
@@ -1,536 +1,546
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 8
9 9 /***** Layout *****/
10 10 #wrapper {background: white;}
11 11
12 12 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
13 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu li {
15 float:left;
16 list-style-type:none;
17 margin: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
19 white-space:nowrap;
20 }
13 21 #top-menu a {color: #fff; padding-right: 4px;}
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23
14 24 #account {float:right;}
15 25
16 26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
17 27 #header a {color:#f8f8f8;}
18 28 #quick-search {float:right;}
19 29
20 30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
21 31 #main-menu ul {margin: 0; padding: 0;}
22 32 #main-menu li {
23 33 float:left;
24 34 list-style-type:none;
25 35 margin: 0px 10px 0px 0px;
26 36 padding: 0px 0px 0px 0px;
27 37 white-space:nowrap;
28 38 }
29 39 #main-menu li a {
30 40 display: block;
31 41 color: #fff;
32 42 text-decoration: none;
33 43 margin: 0;
34 44 padding: 4px 4px 4px 4px;
35 45 background: #2C4056;
36 46 }
37 47 #main-menu li a:hover, #main-menu li a.selected {background:#759FCF;}
38 48
39 49 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
40 50
41 51 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
42 52 * html #sidebar{ width: 17%; }
43 53 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
44 54 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
45 55 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
46 56
47 57 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
48 58 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
49 59 html>body #content {
50 60 height: auto;
51 61 min-height: 600px;
52 62 }
53 63
54 64 #main.nosidebar #sidebar{ display: none; }
55 65 #main.nosidebar #content{ width: auto; border-right: 0; }
56 66
57 67 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
58 68
59 69 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
60 70 #login-form table td {padding: 6px;}
61 71 #login-form label {font-weight: bold;}
62 72
63 73 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
64 74
65 75 /***** Links *****/
66 76 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
67 77 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
68 78 a img{ border: 0; }
69 79
70 80 /***** Tables *****/
71 81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
72 82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
73 83 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
74 84 table.list td.id { width: 2%; text-align: center;}
75 85 table.list td.checkbox { width: 15px; padding: 0px;}
76 86
77 87 tr.issue { text-align: center; white-space: nowrap; }
78 88 tr.issue td.subject, tr.issue td.category { white-space: normal; }
79 89 tr.issue td.subject { text-align: left; }
80 90 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
81 91
82 92 tr.entry { border: 1px solid #f8f8f8; }
83 93 tr.entry td { white-space: nowrap; }
84 94 tr.entry td.filename { width: 30%; }
85 95 tr.entry td.size { text-align: right; font-size: 90%; }
86 96 tr.entry td.revision, tr.entry td.author { text-align: center; }
87 97 tr.entry td.age { text-align: right; }
88 98
89 99 tr.changeset td.author { text-align: center; width: 15%; }
90 100 tr.changeset td.committed_on { text-align: center; width: 15%; }
91 101
92 102 tr.message { height: 2.6em; }
93 103 tr.message td.last_message { font-size: 80%; }
94 104 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
95 105 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
96 106
97 107 tr.user td { width:13%; }
98 108 tr.user td.email { width:18%; }
99 109 tr.user td { white-space: nowrap; }
100 110 tr.user.locked, tr.user.registered { color: #aaa; }
101 111 tr.user.locked a, tr.user.registered a { color: #aaa; }
102 112
103 113 table.list tbody tr:hover { background-color:#ffffdd; }
104 114 table td {padding:2px;}
105 115 table p {margin:0;}
106 116 .odd {background-color:#f6f7f8;}
107 117 .even {background-color: #fff;}
108 118
109 119 .highlight { background-color: #FCFD8D;}
110 120 .highlight.token-1 { background-color: #faa;}
111 121 .highlight.token-2 { background-color: #afa;}
112 122 .highlight.token-3 { background-color: #aaf;}
113 123
114 124 .box{
115 125 padding:6px;
116 126 margin-bottom: 10px;
117 127 background-color:#f6f6f6;
118 128 color:#505050;
119 129 line-height:1.5em;
120 130 border: 1px solid #e4e4e4;
121 131 }
122 132
123 133 div.square {
124 134 border: 1px solid #999;
125 135 float: left;
126 136 margin: .3em .4em 0 .4em;
127 137 overflow: hidden;
128 138 width: .6em; height: .6em;
129 139 }
130 140
131 141 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
132 142 .contextual input {font-size:0.9em;}
133 143
134 144 .splitcontentleft{float:left; width:49%;}
135 145 .splitcontentright{float:right; width:49%;}
136 146 form {display: inline;}
137 147 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
138 148 fieldset {border: 1px solid #e4e4e4; margin:0;}
139 149 legend {color: #484848;}
140 150 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
141 151 textarea.wiki-edit { width: 99%; }
142 152 li p {margin-top: 0;}
143 153 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
144 154
145 155 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
146 156 div#issue-changesets .changeset { padding: 4px;}
147 157 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
148 158 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
149 159
150 160 div#activity dl { margin-left: 2em; }
151 161 div#activity dd { margin-bottom: 1em; }
152 162 div#activity dt { margin-bottom: 1px; }
153 163 div#activity dt .time { color: #777; font-size: 80%; }
154 164 div#activity dd .description { font-style: italic; }
155 165
156 166 .autoscroll {overflow-x: auto; padding:1px; width:100%; margin-bottom: 1.2em;}
157 167 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
158 168
159 169 .pagination {font-size: 90%}
160 170 p.pagination {margin-top:8px;}
161 171
162 172 /***** Tabular forms ******/
163 173 .tabular p{
164 174 margin: 0;
165 175 padding: 5px 0 8px 0;
166 176 padding-left: 180px; /*width of left column containing the label elements*/
167 177 height: 1%;
168 178 clear:left;
169 179 }
170 180
171 181 .tabular label{
172 182 font-weight: bold;
173 183 float: left;
174 184 text-align: right;
175 185 margin-left: -180px; /*width of left column*/
176 186 width: 175px; /*width of labels. Should be smaller than left column to create some right
177 187 margin*/
178 188 }
179 189
180 190 .tabular label.floating{
181 191 font-weight: normal;
182 192 margin-left: 0px;
183 193 text-align: left;
184 194 width: 200px;
185 195 }
186 196
187 197 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
188 198
189 199 .tabular.settings p{ padding-left: 300px; }
190 200 .tabular.settings label{ margin-left: -300px; width: 295px; }
191 201
192 202 .required {color: #bb0000;}
193 203 .summary {font-style: italic;}
194 204
195 205 div.attachments p { margin:4px 0 2px 0; }
196 206
197 207 /***** Flash & error messages ****/
198 208 #errorExplanation, div.flash, .nodata {
199 209 padding: 4px 4px 4px 30px;
200 210 margin-bottom: 12px;
201 211 font-size: 1.1em;
202 212 border: 2px solid;
203 213 }
204 214
205 215 div.flash {margin-top: 8px;}
206 216
207 217 div.flash.error, #errorExplanation {
208 218 background: url(../images/false.png) 8px 5px no-repeat;
209 219 background-color: #ffe3e3;
210 220 border-color: #dd0000;
211 221 color: #550000;
212 222 }
213 223
214 224 div.flash.notice {
215 225 background: url(../images/true.png) 8px 5px no-repeat;
216 226 background-color: #dfffdf;
217 227 border-color: #9fcf9f;
218 228 color: #005f00;
219 229 }
220 230
221 231 .nodata {
222 232 text-align: center;
223 233 background-color: #FFEBC1;
224 234 border-color: #FDBF3B;
225 235 color: #A6750C;
226 236 }
227 237
228 238 #errorExplanation ul { font-size: 0.9em;}
229 239
230 240 /***** Ajax indicator ******/
231 241 #ajax-indicator {
232 242 position: absolute; /* fixed not supported by IE */
233 243 background-color:#eee;
234 244 border: 1px solid #bbb;
235 245 top:35%;
236 246 left:40%;
237 247 width:20%;
238 248 font-weight:bold;
239 249 text-align:center;
240 250 padding:0.6em;
241 251 z-index:100;
242 252 filter:alpha(opacity=50);
243 253 opacity: 0.5;
244 254 -khtml-opacity: 0.5;
245 255 }
246 256
247 257 html>body #ajax-indicator { position: fixed; }
248 258
249 259 #ajax-indicator span {
250 260 background-position: 0% 40%;
251 261 background-repeat: no-repeat;
252 262 background-image: url(../images/loading.gif);
253 263 padding-left: 26px;
254 264 vertical-align: bottom;
255 265 }
256 266
257 267 /***** Calendar *****/
258 268 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
259 269 table.cal thead th {width: 14%;}
260 270 table.cal tbody tr {height: 100px;}
261 271 table.cal th { background-color:#EEEEEE; padding: 4px; }
262 272 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
263 273 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
264 274 table.cal td.odd p.day-num {color: #bbb;}
265 275 table.cal td.today {background:#ffffdd;}
266 276 table.cal td.today p.day-num {font-weight: bold;}
267 277
268 278 /***** Tooltips ******/
269 279 .tooltip{position:relative;z-index:24;}
270 280 .tooltip:hover{z-index:25;color:#000;}
271 281 .tooltip span.tip{display: none; text-align:left;}
272 282
273 283 div.tooltip:hover span.tip{
274 284 display:block;
275 285 position:absolute;
276 286 top:12px; left:24px; width:270px;
277 287 border:1px solid #555;
278 288 background-color:#fff;
279 289 padding: 4px;
280 290 font-size: 0.8em;
281 291 color:#505050;
282 292 }
283 293
284 294 /***** Progress bar *****/
285 295 table.progress {
286 296 border: 1px solid #D7D7D7;
287 297 border-collapse: collapse;
288 298 border-spacing: 0pt;
289 299 empty-cells: show;
290 300 text-align: center;
291 301 float:left;
292 302 margin: 1px 6px 1px 0px;
293 303 }
294 304
295 305 table.progress td { height: 0.9em; }
296 306 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
297 307 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
298 308 table.progress td.open { background: #FFF none repeat scroll 0%; }
299 309 p.pourcent {font-size: 80%;}
300 310 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
301 311
302 312 div#status_by { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; }
303 313
304 314 /***** Tabs *****/
305 315 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
306 316 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
307 317 #content .tabs>ul { bottom:-1px; } /* others */
308 318 #content .tabs ul li {
309 319 float:left;
310 320 list-style-type:none;
311 321 white-space:nowrap;
312 322 margin-right:8px;
313 323 background:#fff;
314 324 }
315 325 #content .tabs ul li a{
316 326 display:block;
317 327 font-size: 0.9em;
318 328 text-decoration:none;
319 329 line-height:1.3em;
320 330 padding:4px 6px 4px 6px;
321 331 border: 1px solid #ccc;
322 332 border-bottom: 1px solid #bbbbbb;
323 333 background-color: #eeeeee;
324 334 color:#777;
325 335 font-weight:bold;
326 336 }
327 337
328 338 #content .tabs ul li a:hover {
329 339 background-color: #ffffdd;
330 340 text-decoration:none;
331 341 }
332 342
333 343 #content .tabs ul li a.selected {
334 344 background-color: #fff;
335 345 border: 1px solid #bbbbbb;
336 346 border-bottom: 1px solid #fff;
337 347 }
338 348
339 349 #content .tabs ul li a.selected:hover {
340 350 background-color: #fff;
341 351 }
342 352
343 353 /***** Diff *****/
344 354 .diff_out { background: #fcc; }
345 355 .diff_in { background: #cfc; }
346 356
347 357 /***** Wiki *****/
348 358 div.wiki table {
349 359 border: 1px solid #505050;
350 360 border-collapse: collapse;
351 361 }
352 362
353 363 div.wiki table, div.wiki td, div.wiki th {
354 364 border: 1px solid #bbb;
355 365 padding: 4px;
356 366 }
357 367
358 368 div.wiki .external {
359 369 background-position: 0% 60%;
360 370 background-repeat: no-repeat;
361 371 padding-left: 12px;
362 372 background-image: url(../images/external.png);
363 373 }
364 374
365 375 div.wiki a.new {
366 376 color: #b73535;
367 377 }
368 378
369 379 div.wiki pre {
370 380 margin: 1em 1em 1em 1.6em;
371 381 padding: 2px;
372 382 background-color: #fafafa;
373 383 border: 1px solid #dadada;
374 384 width:95%;
375 385 overflow-x: auto;
376 386 }
377 387
378 388 div.wiki div.toc {
379 389 background-color: #ffffdd;
380 390 border: 1px solid #e4e4e4;
381 391 padding: 4px;
382 392 line-height: 1.2em;
383 393 margin-bottom: 12px;
384 394 margin-right: 12px;
385 395 display: table
386 396 }
387 397 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
388 398
389 399 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
390 400 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
391 401
392 402 div.wiki div.toc a {
393 403 display: block;
394 404 font-size: 0.9em;
395 405 font-weight: normal;
396 406 text-decoration: none;
397 407 color: #606060;
398 408 }
399 409 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
400 410
401 411 div.wiki div.toc a.heading2 { margin-left: 6px; }
402 412 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
403 413
404 414 /***** My page layout *****/
405 415 .block-receiver {
406 416 border:1px dashed #c0c0c0;
407 417 margin-bottom: 20px;
408 418 padding: 15px 0 15px 0;
409 419 }
410 420
411 421 .mypage-box {
412 422 margin:0 0 20px 0;
413 423 color:#505050;
414 424 line-height:1.5em;
415 425 }
416 426
417 427 .handle {
418 428 cursor: move;
419 429 }
420 430
421 431 a.close-icon {
422 432 display:block;
423 433 margin-top:3px;
424 434 overflow:hidden;
425 435 width:12px;
426 436 height:12px;
427 437 background-repeat: no-repeat;
428 438 cursor:pointer;
429 439 background-image:url('../images/close.png');
430 440 }
431 441
432 442 a.close-icon:hover {
433 443 background-image:url('../images/close_hl.png');
434 444 }
435 445
436 446 /***** Gantt chart *****/
437 447 .gantt_hdr {
438 448 position:absolute;
439 449 top:0;
440 450 height:16px;
441 451 border-top: 1px solid #c0c0c0;
442 452 border-bottom: 1px solid #c0c0c0;
443 453 border-right: 1px solid #c0c0c0;
444 454 text-align: center;
445 455 overflow: hidden;
446 456 }
447 457
448 458 .task {
449 459 position: absolute;
450 460 height:8px;
451 461 font-size:0.8em;
452 462 color:#888;
453 463 padding:0;
454 464 margin:0;
455 465 line-height:0.8em;
456 466 }
457 467
458 468 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
459 469 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
460 470 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
461 471 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
462 472
463 473 /***** Icons *****/
464 474 .icon {
465 475 background-position: 0% 40%;
466 476 background-repeat: no-repeat;
467 477 padding-left: 20px;
468 478 padding-top: 2px;
469 479 padding-bottom: 3px;
470 480 }
471 481
472 482 .icon22 {
473 483 background-position: 0% 40%;
474 484 background-repeat: no-repeat;
475 485 padding-left: 26px;
476 486 line-height: 22px;
477 487 vertical-align: middle;
478 488 }
479 489
480 490 .icon-add { background-image: url(../images/add.png); }
481 491 .icon-edit { background-image: url(../images/edit.png); }
482 492 .icon-copy { background-image: url(../images/copy.png); }
483 493 .icon-del { background-image: url(../images/delete.png); }
484 494 .icon-move { background-image: url(../images/move.png); }
485 495 .icon-save { background-image: url(../images/save.png); }
486 496 .icon-cancel { background-image: url(../images/cancel.png); }
487 497 .icon-pdf { background-image: url(../images/pdf.png); }
488 498 .icon-csv { background-image: url(../images/csv.png); }
489 499 .icon-html { background-image: url(../images/html.png); }
490 500 .icon-image { background-image: url(../images/image.png); }
491 501 .icon-txt { background-image: url(../images/txt.png); }
492 502 .icon-file { background-image: url(../images/file.png); }
493 503 .icon-folder { background-image: url(../images/folder.png); }
494 504 .open .icon-folder { background-image: url(../images/folder_open.png); }
495 505 .icon-package { background-image: url(../images/package.png); }
496 506 .icon-home { background-image: url(../images/home.png); }
497 507 .icon-user { background-image: url(../images/user.png); }
498 508 .icon-mypage { background-image: url(../images/user_page.png); }
499 509 .icon-admin { background-image: url(../images/admin.png); }
500 510 .icon-projects { background-image: url(../images/projects.png); }
501 511 .icon-logout { background-image: url(../images/logout.png); }
502 512 .icon-help { background-image: url(../images/help.png); }
503 513 .icon-attachment { background-image: url(../images/attachment.png); }
504 514 .icon-index { background-image: url(../images/index.png); }
505 515 .icon-history { background-image: url(../images/history.png); }
506 516 .icon-feed { background-image: url(../images/feed.png); }
507 517 .icon-time { background-image: url(../images/time.png); }
508 518 .icon-stats { background-image: url(../images/stats.png); }
509 519 .icon-warning { background-image: url(../images/warning.png); }
510 520 .icon-fav { background-image: url(../images/fav.png); }
511 521 .icon-fav-off { background-image: url(../images/fav_off.png); }
512 522 .icon-reload { background-image: url(../images/reload.png); }
513 523 .icon-lock { background-image: url(../images/locked.png); }
514 524 .icon-unlock { background-image: url(../images/unlock.png); }
515 525 .icon-checked { background-image: url(../images/true.png); }
516 526
517 527 .icon22-projects { background-image: url(../images/22x22/projects.png); }
518 528 .icon22-users { background-image: url(../images/22x22/users.png); }
519 529 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
520 530 .icon22-role { background-image: url(../images/22x22/role.png); }
521 531 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
522 532 .icon22-options { background-image: url(../images/22x22/options.png); }
523 533 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
524 534 .icon22-authent { background-image: url(../images/22x22/authent.png); }
525 535 .icon22-info { background-image: url(../images/22x22/info.png); }
526 536 .icon22-comment { background-image: url(../images/22x22/comment.png); }
527 537 .icon22-package { background-image: url(../images/22x22/package.png); }
528 538 .icon22-settings { background-image: url(../images/22x22/settings.png); }
529 539 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
530 540
531 541 /***** Media print specific styles *****/
532 542 @media print {
533 543 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
534 544 #main { background: #fff; }
535 545 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
536 546 }
General Comments 0
You need to be logged in to leave comments. Login now