##// END OF EJS Templates
Improved Redmine links:...
Jean-Philippe Lang -
r703:fdf842a4c458
parent child
Show More
@@ -1,292 +1,308
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ApplicationHelper
18 module ApplicationHelper
19
19
20 def current_role
20 def current_role
21 @current_role ||= User.current.role_for_project(@project)
21 @current_role ||= User.current.role_for_project(@project)
22 end
22 end
23
23
24 # Return true if user is authorized for controller/action, otherwise false
24 # Return true if user is authorized for controller/action, otherwise false
25 def authorize_for(controller, action)
25 def authorize_for(controller, action)
26 User.current.allowed_to?({:controller => controller, :action => action}, @project)
26 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 end
27 end
28
28
29 # Display a link if user is authorized
29 # Display a link if user is authorized
30 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
30 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
31 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 end
32 end
33
33
34 # Display a link to user's account page
34 # Display a link to user's account page
35 def link_to_user(user)
35 def link_to_user(user)
36 link_to user.name, :controller => 'account', :action => 'show', :id => user
36 link_to user.name, :controller => 'account', :action => 'show', :id => user
37 end
37 end
38
38
39 def link_to_issue(issue)
39 def link_to_issue(issue)
40 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
40 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
41 end
41 end
42
42
43 def toggle_link(name, id, options={})
43 def toggle_link(name, id, options={})
44 onclick = "Element.toggle('#{id}'); "
44 onclick = "Element.toggle('#{id}'); "
45 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
45 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
46 onclick << "return false;"
46 onclick << "return false;"
47 link_to(name, "#", :onclick => onclick)
47 link_to(name, "#", :onclick => onclick)
48 end
48 end
49
49
50 def image_to_function(name, function, html_options = {})
50 def image_to_function(name, function, html_options = {})
51 html_options.symbolize_keys!
51 html_options.symbolize_keys!
52 tag(:input, html_options.merge({
52 tag(:input, html_options.merge({
53 :type => "image", :src => image_path(name),
53 :type => "image", :src => image_path(name),
54 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
54 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
55 }))
55 }))
56 end
56 end
57
57
58 def prompt_to_remote(name, text, param, url, html_options = {})
58 def prompt_to_remote(name, text, param, url, html_options = {})
59 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
59 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
60 link_to name, {}, html_options
60 link_to name, {}, html_options
61 end
61 end
62
62
63 def format_date(date)
63 def format_date(date)
64 return nil unless date
64 return nil unless date
65 @date_format_setting ||= Setting.date_format.to_i
65 @date_format_setting ||= Setting.date_format.to_i
66 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
66 @date_format_setting == 0 ? l_date(date) : date.strftime("%Y-%m-%d")
67 end
67 end
68
68
69 def format_time(time)
69 def format_time(time)
70 return nil unless time
70 return nil unless time
71 @date_format_setting ||= Setting.date_format.to_i
71 @date_format_setting ||= Setting.date_format.to_i
72 time = time.to_time if time.is_a?(String)
72 time = time.to_time if time.is_a?(String)
73 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
73 @date_format_setting == 0 ? l_datetime(time) : (time.strftime("%Y-%m-%d") + ' ' + l_time(time))
74 end
74 end
75
75
76 def day_name(day)
76 def day_name(day)
77 l(:general_day_names).split(',')[day-1]
77 l(:general_day_names).split(',')[day-1]
78 end
78 end
79
79
80 def month_name(month)
80 def month_name(month)
81 l(:actionview_datehelper_select_month_names).split(',')[month-1]
81 l(:actionview_datehelper_select_month_names).split(',')[month-1]
82 end
82 end
83
83
84 def pagination_links_full(paginator, options={}, html_options={})
84 def pagination_links_full(paginator, options={}, html_options={})
85 page_param = options.delete(:page_param) || :page
85 page_param = options.delete(:page_param) || :page
86
86
87 html = ''
87 html = ''
88 html << link_to_remote(('&#171; ' + l(:label_previous)),
88 html << link_to_remote(('&#171; ' + l(:label_previous)),
89 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
89 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
90 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
90 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
91
91
92 html << (pagination_links_each(paginator, options) do |n|
92 html << (pagination_links_each(paginator, options) do |n|
93 link_to_remote(n.to_s,
93 link_to_remote(n.to_s,
94 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
94 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
95 {:href => url_for(:params => options.merge(page_param => n))})
95 {:href => url_for(:params => options.merge(page_param => n))})
96 end || '')
96 end || '')
97
97
98 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
98 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
99 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
99 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
100 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
100 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
101 html
101 html
102 end
102 end
103
103
104 # format text according to system settings
104 # format text according to system settings
105 def textilizable(text, options = {})
105 def textilizable(text, options = {})
106 return "" if text.blank?
106 return "" if text.blank?
107
107
108 # when using an image link, try to use an attachment, if possible
108 # when using an image link, try to use an attachment, if possible
109 attachments = options[:attachments]
109 attachments = options[:attachments]
110 if attachments
110 if attachments
111 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
111 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
112 align = $1
112 align = $1
113 filename = $2
113 filename = $2
114 rf = Regexp.new(filename, Regexp::IGNORECASE)
114 rf = Regexp.new(filename, Regexp::IGNORECASE)
115 # search for the picture in attachments
115 # search for the picture in attachments
116 if found = attachments.detect { |att| att.filename =~ rf }
116 if found = attachments.detect { |att| att.filename =~ rf }
117 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
117 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
118 "!#{align}#{image_url}!"
118 "!#{align}#{image_url}!"
119 else
119 else
120 "!#{align}#{filename}!"
120 "!#{align}#{filename}!"
121 end
121 end
122 end
122 end
123 end
123 end
124
124
125 text = (Setting.text_formatting == 'textile') ?
125 text = (Setting.text_formatting == 'textile') ?
126 Redmine::WikiFormatting.to_html(text) : simple_format(auto_link(h(text)))
126 Redmine::WikiFormatting.to_html(text) : simple_format(auto_link(h(text)))
127
127
128 # different methods for formatting wiki links
128 # different methods for formatting wiki links
129 case options[:wiki_links]
129 case options[:wiki_links]
130 when :local
130 when :local
131 # used for local links to html files
131 # used for local links to html files
132 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
132 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
133 when :anchor
133 when :anchor
134 # used for single-file wiki export
134 # used for single-file wiki export
135 format_wiki_link = Proc.new {|project, title| "##{title}" }
135 format_wiki_link = Proc.new {|project, title| "##{title}" }
136 else
136 else
137 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
137 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
138 end
138 end
139
139
140 project = options[:project] || @project
140 project = options[:project] || @project
141
141
142 # turn wiki links into html links
142 # turn wiki links into html links
143 # example:
143 # example:
144 # [[mypage]]
144 # [[mypage]]
145 # [[mypage|mytext]]
145 # [[mypage|mytext]]
146 # wiki links can refer other project wikis, using project name or identifier:
146 # wiki links can refer other project wikis, using project name or identifier:
147 # [[project:]] -> wiki starting page
147 # [[project:]] -> wiki starting page
148 # [[project:|mytext]]
148 # [[project:|mytext]]
149 # [[project:mypage]]
149 # [[project:mypage]]
150 # [[project:mypage|mytext]]
150 # [[project:mypage|mytext]]
151 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
151 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
152 link_project = project
152 link_project = project
153 page = $1
153 page = $1
154 title = $3
154 title = $3
155 if page =~ /^([^\:]+)\:(.*)$/
155 if page =~ /^([^\:]+)\:(.*)$/
156 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
156 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
157 page = title || $2
157 page = title || $2
158 title = $1 if page.blank?
158 title = $1 if page.blank?
159 end
159 end
160 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)), :class => 'wiki-page')
160
161 if link_project && link_project.wiki
162 # check if page exists
163 wiki_page = link_project.wiki.find_page(page)
164 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
165 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
166 else
167 # project or wiki doesn't exist
168 title || page
169 end
161 end
170 end
162
171
163 # turn issue and revision ids into links
172 # turn issue and revision ids into links
164 # example:
173 # example:
165 # #52 -> <a href="/issues/show/52">#52</a>
174 # #52 -> <a href="/issues/show/52">#52</a>
166 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
175 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
167 text = text.gsub(%r{([\s,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
176 text = text.gsub(%r{([\s,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
168 leading, otype, oid = $1, $2, $3
177 leading, otype, oid = $1, $2, $3
169 link = nil
178 link = nil
170 if otype == 'r'
179 if otype == 'r'
171 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset') if project
180 if project && (changeset = project.changesets.find_by_revision(oid))
181 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
182 :title => truncate(changeset.comments, 100))
183 end
172 else
184 else
173 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue')
185 if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
186 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
187 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
188 link = content_tag('del', link) if issue.closed?
189 end
174 end
190 end
175 leading + (link || "#{otype}#{oid}")
191 leading + (link || "#{otype}#{oid}")
176 end
192 end
177
193
178 text
194 text
179 end
195 end
180
196
181 # Same as Rails' simple_format helper without using paragraphs
197 # Same as Rails' simple_format helper without using paragraphs
182 def simple_format_without_paragraph(text)
198 def simple_format_without_paragraph(text)
183 text.to_s.
199 text.to_s.
184 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
200 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
185 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
201 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
186 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
202 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
187 end
203 end
188
204
189 def error_messages_for(object_name, options = {})
205 def error_messages_for(object_name, options = {})
190 options = options.symbolize_keys
206 options = options.symbolize_keys
191 object = instance_variable_get("@#{object_name}")
207 object = instance_variable_get("@#{object_name}")
192 if object && !object.errors.empty?
208 if object && !object.errors.empty?
193 # build full_messages here with controller current language
209 # build full_messages here with controller current language
194 full_messages = []
210 full_messages = []
195 object.errors.each do |attr, msg|
211 object.errors.each do |attr, msg|
196 next if msg.nil?
212 next if msg.nil?
197 msg = msg.first if msg.is_a? Array
213 msg = msg.first if msg.is_a? Array
198 if attr == "base"
214 if attr == "base"
199 full_messages << l(msg)
215 full_messages << l(msg)
200 else
216 else
201 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
217 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
202 end
218 end
203 end
219 end
204 # retrieve custom values error messages
220 # retrieve custom values error messages
205 if object.errors[:custom_values]
221 if object.errors[:custom_values]
206 object.custom_values.each do |v|
222 object.custom_values.each do |v|
207 v.errors.each do |attr, msg|
223 v.errors.each do |attr, msg|
208 next if msg.nil?
224 next if msg.nil?
209 msg = msg.first if msg.is_a? Array
225 msg = msg.first if msg.is_a? Array
210 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
226 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
211 end
227 end
212 end
228 end
213 end
229 end
214 content_tag("div",
230 content_tag("div",
215 content_tag(
231 content_tag(
216 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
232 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
217 ) +
233 ) +
218 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
234 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
219 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
235 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
220 )
236 )
221 else
237 else
222 ""
238 ""
223 end
239 end
224 end
240 end
225
241
226 def lang_options_for_select(blank=true)
242 def lang_options_for_select(blank=true)
227 (blank ? [["(auto)", ""]] : []) +
243 (blank ? [["(auto)", ""]] : []) +
228 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
244 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.first <=> y.first }
229 end
245 end
230
246
231 def label_tag_for(name, option_tags = nil, options = {})
247 def label_tag_for(name, option_tags = nil, options = {})
232 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
248 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
233 content_tag("label", label_text)
249 content_tag("label", label_text)
234 end
250 end
235
251
236 def labelled_tabular_form_for(name, object, options, &proc)
252 def labelled_tabular_form_for(name, object, options, &proc)
237 options[:html] ||= {}
253 options[:html] ||= {}
238 options[:html].store :class, "tabular"
254 options[:html].store :class, "tabular"
239 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
255 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
240 end
256 end
241
257
242 def check_all_links(form_name)
258 def check_all_links(form_name)
243 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
259 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
244 " | " +
260 " | " +
245 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
261 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
246 end
262 end
247
263
248 def calendar_for(field_id)
264 def calendar_for(field_id)
249 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
265 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
250 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
266 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
251 end
267 end
252
268
253 def wikitoolbar_for(field_id)
269 def wikitoolbar_for(field_id)
254 return '' unless Setting.text_formatting == 'textile'
270 return '' unless Setting.text_formatting == 'textile'
255 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
271 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
256 end
272 end
257 end
273 end
258
274
259 class TabularFormBuilder < ActionView::Helpers::FormBuilder
275 class TabularFormBuilder < ActionView::Helpers::FormBuilder
260 include GLoc
276 include GLoc
261
277
262 def initialize(object_name, object, template, options, proc)
278 def initialize(object_name, object, template, options, proc)
263 set_language_if_valid options.delete(:lang)
279 set_language_if_valid options.delete(:lang)
264 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
280 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
265 end
281 end
266
282
267 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
283 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
268 src = <<-END_SRC
284 src = <<-END_SRC
269 def #{selector}(field, options = {})
285 def #{selector}(field, options = {})
270 return super if options.delete :no_label
286 return super if options.delete :no_label
271 label_text = l(options[:label]) if options[:label]
287 label_text = l(options[:label]) if options[:label]
272 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
288 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
273 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
289 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
274 label = @template.content_tag("label", label_text,
290 label = @template.content_tag("label", label_text,
275 :class => (@object && @object.errors[field] ? "error" : nil),
291 :class => (@object && @object.errors[field] ? "error" : nil),
276 :for => (@object_name.to_s + "_" + field.to_s))
292 :for => (@object_name.to_s + "_" + field.to_s))
277 label + super
293 label + super
278 end
294 end
279 END_SRC
295 END_SRC
280 class_eval src, __FILE__, __LINE__
296 class_eval src, __FILE__, __LINE__
281 end
297 end
282
298
283 def select(field, choices, options = {}, html_options = {})
299 def select(field, choices, options = {}, html_options = {})
284 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
300 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
285 label = @template.content_tag("label", label_text,
301 label = @template.content_tag("label", label_text,
286 :class => (@object && @object.errors[field] ? "error" : nil),
302 :class => (@object && @object.errors[field] ? "error" : nil),
287 :for => (@object_name.to_s + "_" + field.to_s))
303 :for => (@object_name.to_s + "_" + field.to_s))
288 label + super
304 label + super
289 end
305 end
290
306
291 end
307 end
292
308
@@ -1,129 +1,130
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
23 has_many :members, :dependent => :delete_all, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 has_many :users, :through => :members
24 has_many :users, :through => :members
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
26 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
27 has_many :issue_changes, :through => :issues, :source => :journals
27 has_many :issue_changes, :through => :issues, :source => :journals
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
28 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
29 has_many :time_entries, :dependent => :delete_all
29 has_many :time_entries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
30 has_many :queries, :dependent => :delete_all
31 has_many :documents, :dependent => :destroy
31 has_many :documents, :dependent => :destroy
32 has_many :news, :dependent => :delete_all, :include => :author
32 has_many :news, :dependent => :delete_all, :include => :author
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
33 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
34 has_many :boards, :order => "position ASC"
34 has_many :boards, :order => "position ASC"
35 has_one :repository, :dependent => :destroy
35 has_one :repository, :dependent => :destroy
36 has_many :changesets, :through => :repository
36 has_one :wiki, :dependent => :destroy
37 has_one :wiki, :dependent => :destroy
37 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
38 has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
38 acts_as_tree :order => "name", :counter_cache => true
39 acts_as_tree :order => "name", :counter_cache => true
39
40
40 attr_protected :status
41 attr_protected :status
41
42
42 validates_presence_of :name, :description, :identifier
43 validates_presence_of :name, :description, :identifier
43 validates_uniqueness_of :name, :identifier
44 validates_uniqueness_of :name, :identifier
44 validates_associated :custom_values, :on => :update
45 validates_associated :custom_values, :on => :update
45 validates_associated :repository, :wiki
46 validates_associated :repository, :wiki
46 validates_length_of :name, :maximum => 30
47 validates_length_of :name, :maximum => 30
47 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
48 validates_format_of :name, :with => /^[\w\s\'\-]*$/i
48 validates_length_of :description, :maximum => 255
49 validates_length_of :description, :maximum => 255
49 validates_length_of :homepage, :maximum => 30
50 validates_length_of :homepage, :maximum => 30
50 validates_length_of :identifier, :in => 3..12
51 validates_length_of :identifier, :in => 3..12
51 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
52 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
52
53
53 def identifier=(identifier)
54 def identifier=(identifier)
54 super unless identifier_frozen?
55 super unless identifier_frozen?
55 end
56 end
56
57
57 def identifier_frozen?
58 def identifier_frozen?
58 errors[:identifier].nil? && !(new_record? || identifier.blank?)
59 errors[:identifier].nil? && !(new_record? || identifier.blank?)
59 end
60 end
60
61
61 def issues_with_subprojects(include_subprojects=false)
62 def issues_with_subprojects(include_subprojects=false)
62 conditions = nil
63 conditions = nil
63 if include_subprojects && !active_children.empty?
64 if include_subprojects && !active_children.empty?
64 ids = [id] + active_children.collect {|c| c.id}
65 ids = [id] + active_children.collect {|c| c.id}
65 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
66 conditions = ["#{Issue.table_name}.project_id IN (#{ids.join(',')})"]
66 end
67 end
67 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
68 conditions ||= ["#{Issue.table_name}.project_id = ?", id]
68 Issue.with_scope :find => { :conditions => conditions } do
69 Issue.with_scope :find => { :conditions => conditions } do
69 yield
70 yield
70 end
71 end
71 end
72 end
72
73
73 # returns latest created projects
74 # returns latest created projects
74 # non public projects will be returned only if user is a member of those
75 # non public projects will be returned only if user is a member of those
75 def self.latest(user=nil, count=5)
76 def self.latest(user=nil, count=5)
76 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
77 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
77 end
78 end
78
79
79 def self.visible_by(user=nil)
80 def self.visible_by(user=nil)
80 if user && user.admin?
81 if user && user.admin?
81 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
82 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
82 elsif user && user.memberships.any?
83 elsif user && user.memberships.any?
83 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
84 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
84 else
85 else
85 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
86 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
86 end
87 end
87 end
88 end
88
89
89 def active?
90 def active?
90 self.status == STATUS_ACTIVE
91 self.status == STATUS_ACTIVE
91 end
92 end
92
93
93 def archive
94 def archive
94 # Archive subprojects if any
95 # Archive subprojects if any
95 children.each do |subproject|
96 children.each do |subproject|
96 subproject.archive
97 subproject.archive
97 end
98 end
98 update_attribute :status, STATUS_ARCHIVED
99 update_attribute :status, STATUS_ARCHIVED
99 end
100 end
100
101
101 def unarchive
102 def unarchive
102 return false if parent && !parent.active?
103 return false if parent && !parent.active?
103 update_attribute :status, STATUS_ACTIVE
104 update_attribute :status, STATUS_ACTIVE
104 end
105 end
105
106
106 def active_children
107 def active_children
107 children.select {|child| child.active?}
108 children.select {|child| child.active?}
108 end
109 end
109
110
110 # Returns an array of all custom fields enabled for project issues
111 # Returns an array of all custom fields enabled for project issues
111 # (explictly associated custom fields and custom fields enabled for all projects)
112 # (explictly associated custom fields and custom fields enabled for all projects)
112 def custom_fields_for_issues(tracker)
113 def custom_fields_for_issues(tracker)
113 all_custom_fields.select {|c| tracker.custom_fields.include? c }
114 all_custom_fields.select {|c| tracker.custom_fields.include? c }
114 end
115 end
115
116
116 def all_custom_fields
117 def all_custom_fields
117 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
118 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
118 end
119 end
119
120
120 def <=>(project)
121 def <=>(project)
121 name <=> project.name
122 name <=> project.name
122 end
123 end
123
124
124 protected
125 protected
125 def validate
126 def validate
126 errors.add(parent_id, " must be a root project") if parent and parent.parent
127 errors.add(parent_id, " must be a root project") if parent and parent.parent
127 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
128 errors.add_to_base("A project with subprojects can't be a subproject") if parent and children.size > 0
128 end
129 end
129 end
130 end
@@ -1,62 +1,62
1 <%= error_messages_for 'project' %>
1 <%= error_messages_for 'project' %>
2
2
3 <div class="box">
3 <div class="box">
4 <!--[form:project]-->
4 <!--[form:project]-->
5 <p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
5 <p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
6
6
7 <% if User.current.admin? and !@root_projects.empty? %>
7 <% if User.current.admin? and !@root_projects.empty? %>
8 <p><%= f.select :parent_id, (@root_projects.collect {|p| [p.name, p.id]}), { :include_blank => true } %></p>
8 <p><%= f.select :parent_id, (@root_projects.collect {|p| [p.name, p.id]}), { :include_blank => true } %></p>
9 <% end %>
9 <% end %>
10
10
11 <p><%= f.text_area :description, :required => true, :cols => 60, :rows => 5 %><em><%= l(:text_caracters_maximum, 255) %></em></p>
11 <p><%= f.text_area :description, :required => true, :cols => 60, :rows => 5 %><em><%= l(:text_caracters_maximum, 255) %></em></p>
12 <p><%= f.text_field :identifier, :required => true, :size => 15, :disabled => @project.identifier_frozen? %><br /><em><%= l(:text_length_between, 3, 12) %> <%= l(:text_project_identifier_info) unless @project.identifier_frozen? %></em></p>
12 <p><%= f.text_field :identifier, :required => true, :size => 15, :disabled => @project.identifier_frozen? %><br /><em><%= l(:text_length_between, 3, 12) %> <%= l(:text_project_identifier_info) unless @project.identifier_frozen? %></em></p>
13 <p><%= f.text_field :homepage, :size => 40 %></p>
13 <p><%= f.text_field :homepage, :size => 40 %></p>
14 <p><%= f.check_box :is_public %></p>
14 <p><%= f.check_box :is_public %></p>
15 <%= wikitoolbar_for 'project_description' %>
15 <%= wikitoolbar_for 'project_description' %>
16
16
17 <% for @custom_value in @custom_values %>
17 <% for @custom_value in @custom_values %>
18 <p><%= custom_field_tag_with_label @custom_value %></p>
18 <p><%= custom_field_tag_with_label @custom_value %></p>
19 <% end %>
19 <% end %>
20
20
21 <% unless @custom_fields.empty? %>
21 <% unless @custom_fields.empty? %>
22 <p><label><%=l(:label_custom_field_plural)%></label>
22 <p><label><%=l(:label_custom_field_plural)%></label>
23 <% for custom_field in @custom_fields %>
23 <% for custom_field in @custom_fields %>
24 <%= check_box_tag "custom_field_ids[]", custom_field.id, ((@project.custom_fields.include? custom_field) or custom_field.is_for_all?), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
24 <%= check_box_tag "custom_field_ids[]", custom_field.id, ((@project.custom_fields.include? custom_field) or custom_field.is_for_all?), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
25 <%= custom_field.name %>
25 <%= custom_field.name %>
26 <% end %></p>
26 <% end %></p>
27 <% end %>
27 <% end %>
28 <!--[eoform:project]-->
28 <!--[eoform:project]-->
29 </div>
29 </div>
30
30
31 <div class="box">
31 <div class="box">
32 <h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3>
32 <h3><%= check_box_tag "repository_enabled", 1, !@project.repository.nil?, :onclick => "Element.toggle('repository');" %> <%= l(:label_repository) %></h3>
33 <%= hidden_field_tag "repository_enabled", 0 %>
33 <%= hidden_field_tag "repository_enabled", 0 %>
34 <div id="repository">
34 <div id="repository">
35 <p class="tabular"><label>SCM</label><%= scm_select_tag %></p>
35 <p class="tabular"><label>SCM</label><%= scm_select_tag %></p>
36 <div id="repository_fields">
36 <div id="repository_fields">
37 <%= render :partial => 'projects/repository', :locals => {:repository => @project.repository} if @project.repository %>
37 <%= render :partial => 'projects/repository', :locals => {:repository => @project.repository} if @project.repository %>
38 </div>
38 </div>
39 </div>
39 </div>
40 </div>
40 </div>
41 <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %>
41 <%= javascript_tag "Element.hide('repository');" if @project.repository.nil? %>
42
42
43 <div class="box">
43 <div class="box">
44 <h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3>
44 <h3><%= check_box_tag "wiki_enabled", 1, !@project.wiki.nil?, :onclick => "Element.toggle('wiki');" %> <%= l(:label_wiki) %></h3>
45 <%= hidden_field_tag "wiki_enabled", 0 %>
45 <%= hidden_field_tag "wiki_enabled", 0 %>
46 <div id="wiki">
46 <div id="wiki">
47 <% fields_for :wiki, @project.wiki, { :builder => TabularFormBuilder, :lang => current_language} do |wiki| %>
47 <% fields_for :wiki, @project.wiki, { :builder => TabularFormBuilder, :lang => current_language} do |wiki| %>
48 <p><%= wiki.text_field :start_page, :size => 60, :required => true %><br /><em><%= l(:text_unallowed_characters) %>: , . / ? ; |</em></p>
48 <p><%= wiki.text_field :start_page, :size => 60, :required => true %><br /><em><%= l(:text_unallowed_characters) %>: , . / ? ; : |</em></p>
49 <% # content_tag("div", "", :id => "wiki_start_page_auto_complete", :class => "auto_complete") +
49 <% # content_tag("div", "", :id => "wiki_start_page_auto_complete", :class => "auto_complete") +
50 # auto_complete_field("wiki_start_page", { :url => { :controller => 'wiki', :action => 'auto_complete_for_wiki_page', :id => @project } })
50 # auto_complete_field("wiki_start_page", { :url => { :controller => 'wiki', :action => 'auto_complete_for_wiki_page', :id => @project } })
51 %>
51 %>
52 <% end %>
52 <% end %>
53 </div>
53 </div>
54 <%= javascript_tag "Element.hide('wiki');" if @project.wiki.nil? %>
54 <%= javascript_tag "Element.hide('wiki');" if @project.wiki.nil? %>
55 </div>
55 </div>
56
56
57 <% content_for :header_tags do %>
57 <% content_for :header_tags do %>
58 <%= javascript_include_tag 'calendar/calendar' %>
58 <%= javascript_include_tag 'calendar/calendar' %>
59 <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
59 <%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>
60 <%= javascript_include_tag 'calendar/calendar-setup' %>
60 <%= javascript_include_tag 'calendar/calendar-setup' %>
61 <%= stylesheet_link_tag 'calendar' %>
61 <%= stylesheet_link_tag 'calendar' %>
62 <% end %>
62 <% end %>
@@ -1,5 +1,3
1 <div class="wiki">
1 <div class="wiki">
2 <% cache "wiki/show/#{content.page.id}/#{content.version}" do %>
2 <%= textilizable content.text, :attachments => content.page.attachments %>
3 <%= textilizable content.text, :attachments => content.page.attachments %>
4 <% end %>
5 </div>
3 </div>
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,702 +1,706
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
1 /* andreas08 - an open source xhtml/css website layout by Andreas Viklund - http://andreasviklund.com . Free to use in any way and for any purpose as long as the proper credits are given to the original designer. Version: 1.0, November 28, 2005 */
2 /* Edited by Jean-Philippe Lang *>
2 /* Edited by Jean-Philippe Lang *>
3 /**************** Body and tag styles ****************/
3 /**************** Body and tag styles ****************/
4
4
5 #header * {margin:0; padding:0;}
5 #header * {margin:0; padding:0;}
6 p, ul, ol, li {margin:0; padding:0;}
6 p, ul, ol, li {margin:0; padding:0;}
7
7
8 body{
8 body{
9 font:76% Verdana,Tahoma,Arial,sans-serif;
9 font:76% Verdana,Tahoma,Arial,sans-serif;
10 line-height:1.4em;
10 line-height:1.4em;
11 text-align:center;
11 text-align:center;
12 color:#303030;
12 color:#303030;
13 background:#e8eaec;
13 background:#e8eaec;
14 margin:0;
14 margin:0;
15 }
15 }
16
16
17 a{color:#467aa7;font-weight:bold;text-decoration:none;background-color:inherit;}
17 a{color:#467aa7;font-weight:bold;text-decoration:none;background-color:inherit;}
18 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
18 a:hover{color:#2a5a8a; text-decoration:none; background-color:inherit;}
19 a img{border:none;}
19 a img{border:none;}
20
20
21 p{margin:0 0 1em 0;}
21 p{margin:0 0 1em 0;}
22 p form{margin-top:0; margin-bottom:20px;}
22 p form{margin-top:0; margin-bottom:20px;}
23
23
24 img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
24 img.left,img.center,img.right{padding:4px; border:1px solid #a0a0a0;}
25 img.left{float:left; margin:0 12px 5px 0;}
25 img.left{float:left; margin:0 12px 5px 0;}
26 img.center{display:block; margin:0 auto 5px auto;}
26 img.center{display:block; margin:0 auto 5px auto;}
27 img.right{float:right; margin:0 0 5px 12px;}
27 img.right{float:right; margin:0 0 5px 12px;}
28
28
29 /**************** Header and navigation styles ****************/
29 /**************** Header and navigation styles ****************/
30
30
31 #container{
31 #container{
32 width:100%;
32 width:100%;
33 min-width: 800px;
33 min-width: 800px;
34 margin:0;
34 margin:0;
35 padding:0;
35 padding:0;
36 text-align:left;
36 text-align:left;
37 background:#ffffff;
37 background:#ffffff;
38 color:#303030;
38 color:#303030;
39 }
39 }
40
40
41 #header{
41 #header{
42 height:4.5em;
42 height:4.5em;
43 margin:0;
43 margin:0;
44 background:#467aa7;
44 background:#467aa7;
45 color:#ffffff;
45 color:#ffffff;
46 margin-bottom:1px;
46 margin-bottom:1px;
47 }
47 }
48
48
49 #header h1{
49 #header h1{
50 padding:10px 0 0 20px;
50 padding:10px 0 0 20px;
51 font-size:2em;
51 font-size:2em;
52 background-color:inherit;
52 background-color:inherit;
53 color:#fff;
53 color:#fff;
54 letter-spacing:-1px;
54 letter-spacing:-1px;
55 font-weight:bold;
55 font-weight:bold;
56 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
56 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
57 }
57 }
58
58
59 #header h2{
59 #header h2{
60 margin:3px 0 0 40px;
60 margin:3px 0 0 40px;
61 font-size:1.5em;
61 font-size:1.5em;
62 background-color:inherit;
62 background-color:inherit;
63 color:#f0f2f4;
63 color:#f0f2f4;
64 letter-spacing:-1px;
64 letter-spacing:-1px;
65 font-weight:normal;
65 font-weight:normal;
66 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
66 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
67 }
67 }
68
68
69 #header a {color:#fff;}
69 #header a {color:#fff;}
70
70
71 #navigation{
71 #navigation{
72 height:2.2em;
72 height:2.2em;
73 line-height:2.2em;
73 line-height:2.2em;
74 margin:0;
74 margin:0;
75 background:#578bb8;
75 background:#578bb8;
76 color:#ffffff;
76 color:#ffffff;
77 }
77 }
78
78
79 #navigation li{
79 #navigation li{
80 float:left;
80 float:left;
81 list-style-type:none;
81 list-style-type:none;
82 border-right:1px solid #ffffff;
82 border-right:1px solid #ffffff;
83 white-space:nowrap;
83 white-space:nowrap;
84 }
84 }
85
85
86 #navigation li.right {
86 #navigation li.right {
87 float:right;
87 float:right;
88 list-style-type:none;
88 list-style-type:none;
89 border-right:0;
89 border-right:0;
90 border-left:1px solid #ffffff;
90 border-left:1px solid #ffffff;
91 white-space:nowrap;
91 white-space:nowrap;
92 }
92 }
93
93
94 #navigation li a{
94 #navigation li a{
95 display:block;
95 display:block;
96 padding:0px 10px 0px 22px;
96 padding:0px 10px 0px 22px;
97 font-size:0.8em;
97 font-size:0.8em;
98 font-weight:normal;
98 font-weight:normal;
99 text-decoration:none;
99 text-decoration:none;
100 background-color:inherit;
100 background-color:inherit;
101 color: #ffffff;
101 color: #ffffff;
102 }
102 }
103
103
104 #navigation li.submenu {background:url(../images/arrow_down.png) 96% 80% no-repeat;}
104 #navigation li.submenu {background:url(../images/arrow_down.png) 96% 80% no-repeat;}
105 #navigation li.submenu a {padding:0px 16px 0px 22px;}
105 #navigation li.submenu a {padding:0px 16px 0px 22px;}
106 * html #navigation a {width:1%;}
106 * html #navigation a {width:1%;}
107
107
108 #navigation .selected,#navigation a:hover{
108 #navigation .selected,#navigation a:hover{
109 color:#ffffff;
109 color:#ffffff;
110 text-decoration:none;
110 text-decoration:none;
111 background-color: #80b0da;
111 background-color: #80b0da;
112 }
112 }
113
113
114 /**************** Icons *******************/
114 /**************** Icons *******************/
115 .icon {
115 .icon {
116 background-position: 0% 40%;
116 background-position: 0% 40%;
117 background-repeat: no-repeat;
117 background-repeat: no-repeat;
118 padding-left: 20px;
118 padding-left: 20px;
119 padding-top: 2px;
119 padding-top: 2px;
120 padding-bottom: 3px;
120 padding-bottom: 3px;
121 vertical-align: middle;
121 vertical-align: middle;
122 }
122 }
123
123
124 #navigation .icon {
124 #navigation .icon {
125 background-position: 4px 50%;
125 background-position: 4px 50%;
126 }
126 }
127
127
128 .icon22 {
128 .icon22 {
129 background-position: 0% 40%;
129 background-position: 0% 40%;
130 background-repeat: no-repeat;
130 background-repeat: no-repeat;
131 padding-left: 26px;
131 padding-left: 26px;
132 line-height: 22px;
132 line-height: 22px;
133 vertical-align: middle;
133 vertical-align: middle;
134 }
134 }
135
135
136 .icon-add { background-image: url(../images/add.png); }
136 .icon-add { background-image: url(../images/add.png); }
137 .icon-edit { background-image: url(../images/edit.png); }
137 .icon-edit { background-image: url(../images/edit.png); }
138 .icon-del { background-image: url(../images/delete.png); }
138 .icon-del { background-image: url(../images/delete.png); }
139 .icon-move { background-image: url(../images/move.png); }
139 .icon-move { background-image: url(../images/move.png); }
140 .icon-save { background-image: url(../images/save.png); }
140 .icon-save { background-image: url(../images/save.png); }
141 .icon-cancel { background-image: url(../images/cancel.png); }
141 .icon-cancel { background-image: url(../images/cancel.png); }
142 .icon-pdf { background-image: url(../images/pdf.png); }
142 .icon-pdf { background-image: url(../images/pdf.png); }
143 .icon-csv { background-image: url(../images/csv.png); }
143 .icon-csv { background-image: url(../images/csv.png); }
144 .icon-html { background-image: url(../images/html.png); }
144 .icon-html { background-image: url(../images/html.png); }
145 .icon-image { background-image: url(../images/image.png); }
145 .icon-image { background-image: url(../images/image.png); }
146 .icon-txt { background-image: url(../images/txt.png); }
146 .icon-txt { background-image: url(../images/txt.png); }
147 .icon-file { background-image: url(../images/file.png); }
147 .icon-file { background-image: url(../images/file.png); }
148 .icon-folder { background-image: url(../images/folder.png); }
148 .icon-folder { background-image: url(../images/folder.png); }
149 .icon-package { background-image: url(../images/package.png); }
149 .icon-package { background-image: url(../images/package.png); }
150 .icon-home { background-image: url(../images/home.png); }
150 .icon-home { background-image: url(../images/home.png); }
151 .icon-user { background-image: url(../images/user.png); }
151 .icon-user { background-image: url(../images/user.png); }
152 .icon-mypage { background-image: url(../images/user_page.png); }
152 .icon-mypage { background-image: url(../images/user_page.png); }
153 .icon-admin { background-image: url(../images/admin.png); }
153 .icon-admin { background-image: url(../images/admin.png); }
154 .icon-projects { background-image: url(../images/projects.png); }
154 .icon-projects { background-image: url(../images/projects.png); }
155 .icon-logout { background-image: url(../images/logout.png); }
155 .icon-logout { background-image: url(../images/logout.png); }
156 .icon-help { background-image: url(../images/help.png); }
156 .icon-help { background-image: url(../images/help.png); }
157 .icon-attachment { background-image: url(../images/attachment.png); }
157 .icon-attachment { background-image: url(../images/attachment.png); }
158 .icon-index { background-image: url(../images/index.png); }
158 .icon-index { background-image: url(../images/index.png); }
159 .icon-history { background-image: url(../images/history.png); }
159 .icon-history { background-image: url(../images/history.png); }
160 .icon-feed { background-image: url(../images/feed.png); }
160 .icon-feed { background-image: url(../images/feed.png); }
161 .icon-time { background-image: url(../images/time.png); }
161 .icon-time { background-image: url(../images/time.png); }
162 .icon-stats { background-image: url(../images/stats.png); }
162 .icon-stats { background-image: url(../images/stats.png); }
163 .icon-warning { background-image: url(../images/warning.png); }
163 .icon-warning { background-image: url(../images/warning.png); }
164 .icon-fav { background-image: url(../images/fav.png); }
164 .icon-fav { background-image: url(../images/fav.png); }
165 .icon-fav-off { background-image: url(../images/fav_off.png); }
165 .icon-fav-off { background-image: url(../images/fav_off.png); }
166 .icon-reload { background-image: url(../images/reload.png); }
166 .icon-reload { background-image: url(../images/reload.png); }
167 .icon-lock { background-image: url(../images/locked.png); }
167 .icon-lock { background-image: url(../images/locked.png); }
168 .icon-unlock { background-image: url(../images/unlock.png); }
168 .icon-unlock { background-image: url(../images/unlock.png); }
169
169
170 .icon22-projects { background-image: url(../images/22x22/projects.png); }
170 .icon22-projects { background-image: url(../images/22x22/projects.png); }
171 .icon22-users { background-image: url(../images/22x22/users.png); }
171 .icon22-users { background-image: url(../images/22x22/users.png); }
172 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
172 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
173 .icon22-role { background-image: url(../images/22x22/role.png); }
173 .icon22-role { background-image: url(../images/22x22/role.png); }
174 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
174 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
175 .icon22-options { background-image: url(../images/22x22/options.png); }
175 .icon22-options { background-image: url(../images/22x22/options.png); }
176 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
176 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
177 .icon22-authent { background-image: url(../images/22x22/authent.png); }
177 .icon22-authent { background-image: url(../images/22x22/authent.png); }
178 .icon22-info { background-image: url(../images/22x22/info.png); }
178 .icon22-info { background-image: url(../images/22x22/info.png); }
179 .icon22-comment { background-image: url(../images/22x22/comment.png); }
179 .icon22-comment { background-image: url(../images/22x22/comment.png); }
180 .icon22-package { background-image: url(../images/22x22/package.png); }
180 .icon22-package { background-image: url(../images/22x22/package.png); }
181 .icon22-settings { background-image: url(../images/22x22/settings.png); }
181 .icon22-settings { background-image: url(../images/22x22/settings.png); }
182
182
183 /**************** Content styles ****************/
183 /**************** Content styles ****************/
184
184
185 html>body #content {
185 html>body #content {
186 height: auto;
186 height: auto;
187 min-height: 500px;
187 min-height: 500px;
188 }
188 }
189
189
190 #content{
190 #content{
191 width: auto;
191 width: auto;
192 height:500px;
192 height:500px;
193 font-size:0.9em;
193 font-size:0.9em;
194 padding:20px 10px 10px 20px;
194 padding:20px 10px 10px 20px;
195 margin-left: 120px;
195 margin-left: 120px;
196 border-left: 1px dashed #c0c0c0;
196 border-left: 1px dashed #c0c0c0;
197
197
198 }
198 }
199
199
200 #content h2, #content div.wiki h1 {
200 #content h2, #content div.wiki h1 {
201 display:block;
201 display:block;
202 margin:0 0 16px 0;
202 margin:0 0 16px 0;
203 font-size:1.7em;
203 font-size:1.7em;
204 font-weight:normal;
204 font-weight:normal;
205 letter-spacing:-1px;
205 letter-spacing:-1px;
206 color:#606060;
206 color:#606060;
207 background-color:inherit;
207 background-color:inherit;
208 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
208 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
209 }
209 }
210
210
211 #content h2 a{font-weight:normal;}
211 #content h2 a{font-weight:normal;}
212 #content h3{margin:0 0 12px 0; font-size:1.4em;color:#707070;font-family: Trebuchet MS,Georgia,"Times New Roman",serif;}
212 #content h3{margin:0 0 12px 0; font-size:1.4em;color:#707070;font-family: Trebuchet MS,Georgia,"Times New Roman",serif;}
213 #content h4{font-size: 1em; margin-bottom: 12px; margin-top: 20px; font-weight: normal; border-bottom: dotted 1px #c0c0c0;}
213 #content h4{font-size: 1em; margin-bottom: 12px; margin-top: 20px; font-weight: normal; border-bottom: dotted 1px #c0c0c0;}
214 #content a:hover,#subcontent a:hover{text-decoration:underline;}
214 #content a:hover,#subcontent a:hover{text-decoration:underline;}
215 #content ul,#content ol{margin:0 5px 16px 35px;}
215 #content ul,#content ol{margin:0 5px 16px 35px;}
216 #content dl{margin:0 5px 10px 25px;}
216 #content dl{margin:0 5px 10px 25px;}
217 #content dt{font-weight:bold; margin-bottom:5px;}
217 #content dt{font-weight:bold; margin-bottom:5px;}
218 #content dd{margin:0 0 10px 15px;}
218 #content dd{margin:0 0 10px 15px;}
219
219
220 #content .tabs{height: 2.6em;}
220 #content .tabs{height: 2.6em;}
221 #content .tabs ul{margin:0;}
221 #content .tabs ul{margin:0;}
222 #content .tabs ul li{
222 #content .tabs ul li{
223 float:left;
223 float:left;
224 list-style-type:none;
224 list-style-type:none;
225 white-space:nowrap;
225 white-space:nowrap;
226 margin-right:8px;
226 margin-right:8px;
227 background:#fff;
227 background:#fff;
228 }
228 }
229 #content .tabs ul li a{
229 #content .tabs ul li a{
230 display:block;
230 display:block;
231 font-size: 0.9em;
231 font-size: 0.9em;
232 text-decoration:none;
232 text-decoration:none;
233 line-height:1em;
233 line-height:1em;
234 padding:4px;
234 padding:4px;
235 border: 1px solid #c0c0c0;
235 border: 1px solid #c0c0c0;
236 }
236 }
237
237
238 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
238 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
239 background-color: #80b0da;
239 background-color: #80b0da;
240 border: 1px solid #80b0da;
240 border: 1px solid #80b0da;
241 color: #fff;
241 color: #fff;
242 text-decoration:none;
242 text-decoration:none;
243 }
243 }
244
244
245 /***********************************************/
245 /***********************************************/
246
246
247 form {display: inline;}
247 form {display: inline;}
248 blockquote {padding-left: 6px; border-left: 2px solid #ccc;}
248 blockquote {padding-left: 6px; border-left: 2px solid #ccc;}
249 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
249 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
250
250
251 input.button-small {font-size: 0.8em;}
251 input.button-small {font-size: 0.8em;}
252 textarea.wiki-edit { width: 99.5%; }
252 textarea.wiki-edit { width: 99.5%; }
253 .select-small {font-size: 0.8em;}
253 .select-small {font-size: 0.8em;}
254 label {font-weight: bold; font-size: 1em; color: #505050;}
254 label {font-weight: bold; font-size: 1em; color: #505050;}
255 fieldset {border:1px solid #c0c0c0; padding: 6px;}
255 fieldset {border:1px solid #c0c0c0; padding: 6px;}
256 legend {color: #505050;}
256 legend {color: #505050;}
257 .required {color: #bb0000;}
257 .required {color: #bb0000;}
258 .odd {background-color:#f6f7f8;}
258 .odd {background-color:#f6f7f8;}
259 .even {background-color: #fff;}
259 .even {background-color: #fff;}
260 hr { border:0; border-top: dotted 1px #fff; border-bottom: dotted 1px #c0c0c0; }
260 hr { border:0; border-top: dotted 1px #fff; border-bottom: dotted 1px #c0c0c0; }
261 table p {margin:0; padding:0;}
261 table p {margin:0; padding:0;}
262
262
263 .highlight { background-color: #FCFD8D;}
263 .highlight { background-color: #FCFD8D;}
264
264
265 div.square {
265 div.square {
266 border: 1px solid #999;
266 border: 1px solid #999;
267 float: left;
267 float: left;
268 margin: .4em .5em 0 0;
268 margin: .4em .5em 0 0;
269 overflow: hidden;
269 overflow: hidden;
270 width: .6em; height: .6em;
270 width: .6em; height: .6em;
271 }
271 }
272
272
273 ul.documents {
273 ul.documents {
274 list-style-type: none;
274 list-style-type: none;
275 padding: 0;
275 padding: 0;
276 margin: 0;
276 margin: 0;
277 }
277 }
278
278
279 ul.documents li {
279 ul.documents li {
280 background-image: url(../images/32x32/file.png);
280 background-image: url(../images/32x32/file.png);
281 background-repeat: no-repeat;
281 background-repeat: no-repeat;
282 background-position: 0 1px;
282 background-position: 0 1px;
283 padding-left: 36px;
283 padding-left: 36px;
284 margin-bottom: 10px;
284 margin-bottom: 10px;
285 margin-left: -37px;
285 margin-left: -37px;
286 }
286 }
287
287
288 /********** Table used to display lists of things ***********/
288 /********** Table used to display lists of things ***********/
289
289
290 table.list {
290 table.list {
291 width:100%;
291 width:100%;
292 border-collapse: collapse;
292 border-collapse: collapse;
293 border: 1px dotted #d0d0d0;
293 border: 1px dotted #d0d0d0;
294 margin-bottom: 6px;
294 margin-bottom: 6px;
295 }
295 }
296
296
297 table.with-cells td {
297 table.with-cells td {
298 border: 1px solid #d7d7d7;
298 border: 1px solid #d7d7d7;
299 }
299 }
300
300
301 table.list td {
301 table.list td {
302 padding:2px;
302 padding:2px;
303 }
303 }
304
304
305 table.list thead th {
305 table.list thead th {
306 text-align: center;
306 text-align: center;
307 background: #eee;
307 background: #eee;
308 border: 1px solid #d7d7d7;
308 border: 1px solid #d7d7d7;
309 color: #777;
309 color: #777;
310 }
310 }
311
311
312 table.list tbody th {
312 table.list tbody th {
313 font-weight: bold;
313 font-weight: bold;
314 background: #eed;
314 background: #eed;
315 border: 1px solid #d7d7d7;
315 border: 1px solid #d7d7d7;
316 color: #777;
316 color: #777;
317 }
317 }
318
318
319 /*========== Drop down menu ==============*/
319 /*========== Drop down menu ==============*/
320 div.menu {
320 div.menu {
321 background-color: #FFFFFF;
321 background-color: #FFFFFF;
322 border-style: solid;
322 border-style: solid;
323 border-width: 1px;
323 border-width: 1px;
324 border-color: #7F9DB9;
324 border-color: #7F9DB9;
325 position: absolute;
325 position: absolute;
326 top: 0px;
326 top: 0px;
327 left: 0px;
327 left: 0px;
328 padding: 0;
328 padding: 0;
329 visibility: hidden;
329 visibility: hidden;
330 z-index: 101;
330 z-index: 101;
331 }
331 }
332
332
333 div.menu a.menuItem {
333 div.menu a.menuItem {
334 font-size: 10px;
334 font-size: 10px;
335 font-weight: normal;
335 font-weight: normal;
336 line-height: 2em;
336 line-height: 2em;
337 color: #000000;
337 color: #000000;
338 background-color: #FFFFFF;
338 background-color: #FFFFFF;
339 cursor: default;
339 cursor: default;
340 display: block;
340 display: block;
341 padding: 0 1em;
341 padding: 0 1em;
342 margin: 0;
342 margin: 0;
343 border: 0;
343 border: 0;
344 text-decoration: none;
344 text-decoration: none;
345 white-space: nowrap;
345 white-space: nowrap;
346 }
346 }
347
347
348 div.menu a.menuItem:hover, div.menu a.menuItemHighlight {
348 div.menu a.menuItem:hover, div.menu a.menuItemHighlight {
349 background-color: #80b0da;
349 background-color: #80b0da;
350 color: #ffffff;
350 color: #ffffff;
351 }
351 }
352
352
353 div.menu a.menuItem span.menuItemText {}
353 div.menu a.menuItem span.menuItemText {}
354
354
355 div.menu a.menuItem span.menuItemArrow {
355 div.menu a.menuItem span.menuItemArrow {
356 margin-right: -.75em;
356 margin-right: -.75em;
357 }
357 }
358
358
359 /**************** Sidebar styles ****************/
359 /**************** Sidebar styles ****************/
360
360
361 #subcontent{
361 #subcontent{
362 position: absolute;
362 position: absolute;
363 left: 0px;
363 left: 0px;
364 width:95px;
364 width:95px;
365 padding:20px 20px 10px 5px;
365 padding:20px 20px 10px 5px;
366 overflow: hidden;
366 overflow: hidden;
367 }
367 }
368
368
369 #subcontent h2{
369 #subcontent h2{
370 display:block;
370 display:block;
371 margin:0 0 5px 0;
371 margin:0 0 5px 0;
372 font-size:1.0em;
372 font-size:1.0em;
373 font-weight:bold;
373 font-weight:bold;
374 text-align:left;
374 text-align:left;
375 color:#606060;
375 color:#606060;
376 background-color:inherit;
376 background-color:inherit;
377 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
377 font-family: Trebuchet MS,Georgia,"Times New Roman",serif;
378 }
378 }
379
379
380 #subcontent p{margin:0 0 16px 0; font-size:0.9em;}
380 #subcontent p{margin:0 0 16px 0; font-size:0.9em;}
381
381
382 /**************** Menublock styles ****************/
382 /**************** Menublock styles ****************/
383
383
384 .menublock{margin:0 0 20px 8px; font-size:0.8em;}
384 .menublock{margin:0 0 20px 8px; font-size:0.8em;}
385 .menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
385 .menublock li{list-style:none; display:block; padding:1px; margin-bottom:0px;}
386 .menublock li a{font-weight:bold; text-decoration:none;}
386 .menublock li a{font-weight:bold; text-decoration:none;}
387 .menublock li a:hover{text-decoration:none;}
387 .menublock li a:hover{text-decoration:none;}
388 .menublock li ul{margin:0; font-size:1em; font-weight:normal;}
388 .menublock li ul{margin:0; font-size:1em; font-weight:normal;}
389 .menublock li ul li{margin-bottom:0;}
389 .menublock li ul li{margin-bottom:0;}
390 .menublock li ul a{font-weight:normal;}
390 .menublock li ul a{font-weight:normal;}
391
391
392 /**************** Footer styles ****************/
392 /**************** Footer styles ****************/
393
393
394 #footer{
394 #footer{
395 clear:both;
395 clear:both;
396 padding:5px 0;
396 padding:5px 0;
397 margin:0;
397 margin:0;
398 font-size:0.9em;
398 font-size:0.9em;
399 color:#f0f0f0;
399 color:#f0f0f0;
400 background:#467aa7;
400 background:#467aa7;
401 }
401 }
402
402
403 #footer p{padding:0; margin:0; text-align:center;}
403 #footer p{padding:0; margin:0; text-align:center;}
404 #footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
404 #footer a{color:#f0f0f0; background-color:inherit; font-weight:bold;}
405 #footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
405 #footer a:hover{color:#ffffff; background-color:inherit; text-decoration: underline;}
406
406
407 /**************** Misc classes and styles ****************/
407 /**************** Misc classes and styles ****************/
408
408
409 .splitcontentleft{float:left; width:49%;}
409 .splitcontentleft{float:left; width:49%;}
410 .splitcontentright{float:right; width:49%;}
410 .splitcontentright{float:right; width:49%;}
411 .clear{clear:both;}
411 .clear{clear:both;}
412 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
412 .small{font-size:0.8em;line-height:1.4em;padding:0 0 0 0;}
413 .hide{display:none;}
413 .hide{display:none;}
414 .textcenter{text-align:center;}
414 .textcenter{text-align:center;}
415 .textright{text-align:right;}
415 .textright{text-align:right;}
416 .important{color:#f02025; background-color:inherit; font-weight:bold;}
416 .important{color:#f02025; background-color:inherit; font-weight:bold;}
417
417
418 .box{
418 .box{
419 margin:0 0 20px 0;
419 margin:0 0 20px 0;
420 padding:10px;
420 padding:10px;
421 border:1px solid #c0c0c0;
421 border:1px solid #c0c0c0;
422 background-color:#fafbfc;
422 background-color:#fafbfc;
423 color:#505050;
423 color:#505050;
424 line-height:1.5em;
424 line-height:1.5em;
425 }
425 }
426
426
427 a.close-icon {
427 a.close-icon {
428 display:block;
428 display:block;
429 margin-top:3px;
429 margin-top:3px;
430 overflow:hidden;
430 overflow:hidden;
431 width:12px;
431 width:12px;
432 height:12px;
432 height:12px;
433 background-repeat: no-repeat;
433 background-repeat: no-repeat;
434 cursor:pointer;
434 cursor:pointer;
435 background-image:url('../images/close.png');
435 background-image:url('../images/close.png');
436 }
436 }
437
437
438 a.close-icon:hover {
438 a.close-icon:hover {
439 background-image:url('../images/close_hl.png');
439 background-image:url('../images/close_hl.png');
440 }
440 }
441
441
442 .rightbox{
442 .rightbox{
443 background: #fafbfc;
443 background: #fafbfc;
444 border: 1px solid #c0c0c0;
444 border: 1px solid #c0c0c0;
445 float: right;
445 float: right;
446 padding: 8px;
446 padding: 8px;
447 position: relative;
447 position: relative;
448 margin: 0 5px 5px;
448 margin: 0 5px 5px;
449 }
449 }
450
450
451 div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
451 div.attachments {padding-left: 6px; border-left: 2px solid #ccc; margin-bottom: 8px;}
452 div.attachments p {margin-bottom:2px;}
452 div.attachments p {margin-bottom:2px;}
453
453
454 .overlay{
454 .overlay{
455 position: absolute;
455 position: absolute;
456 margin-left:0;
456 margin-left:0;
457 z-index: 50;
457 z-index: 50;
458 }
458 }
459
459
460 .layout-active {
460 .layout-active {
461 background: #ECF3E1;
461 background: #ECF3E1;
462 }
462 }
463
463
464 .block-receiver {
464 .block-receiver {
465 border:1px dashed #c0c0c0;
465 border:1px dashed #c0c0c0;
466 margin-bottom: 20px;
466 margin-bottom: 20px;
467 padding: 15px 0 15px 0;
467 padding: 15px 0 15px 0;
468 }
468 }
469
469
470 .mypage-box {
470 .mypage-box {
471 margin:0 0 20px 0;
471 margin:0 0 20px 0;
472 color:#505050;
472 color:#505050;
473 line-height:1.5em;
473 line-height:1.5em;
474 }
474 }
475
475
476 .handle {
476 .handle {
477 cursor: move;
477 cursor: move;
478 }
478 }
479
479
480 .login {
480 .login {
481 width: 50%;
481 width: 50%;
482 text-align: left;
482 text-align: left;
483 }
483 }
484
484
485 img.calendar-trigger {
485 img.calendar-trigger {
486 cursor: pointer;
486 cursor: pointer;
487 vertical-align: middle;
487 vertical-align: middle;
488 margin-left: 4px;
488 margin-left: 4px;
489 }
489 }
490
490
491 #history p {
491 #history p {
492 margin-left: 34px;
492 margin-left: 34px;
493 }
493 }
494
494
495 .progress {
495 .progress {
496 border: 1px solid #D7D7D7;
496 border: 1px solid #D7D7D7;
497 border-collapse: collapse;
497 border-collapse: collapse;
498 border-spacing: 0pt;
498 border-spacing: 0pt;
499 empty-cells: show;
499 empty-cells: show;
500 padding: 3px;
500 padding: 3px;
501 width: 40em;
501 width: 40em;
502 text-align: center;
502 text-align: center;
503 }
503 }
504
504
505 .progress td { height: 1em; }
505 .progress td { height: 1em; }
506 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
506 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
507 .progress .open { background: #FFF none repeat scroll 0%; }
507 .progress .open { background: #FFF none repeat scroll 0%; }
508
508
509 /***** Contextual links div *****/
509 /***** Contextual links div *****/
510 .contextual {
510 .contextual {
511 float: right;
511 float: right;
512 font-size: 0.8em;
512 font-size: 0.8em;
513 line-height: 16px;
513 line-height: 16px;
514 padding: 2px;
514 padding: 2px;
515 }
515 }
516
516
517 .contextual select, .contextual input {
517 .contextual select, .contextual input {
518 font-size: 1em;
518 font-size: 1em;
519 }
519 }
520
520
521 /***** Gantt chart *****/
521 /***** Gantt chart *****/
522 .gantt_hdr {
522 .gantt_hdr {
523 position:absolute;
523 position:absolute;
524 top:0;
524 top:0;
525 height:16px;
525 height:16px;
526 border-top: 1px solid #c0c0c0;
526 border-top: 1px solid #c0c0c0;
527 border-bottom: 1px solid #c0c0c0;
527 border-bottom: 1px solid #c0c0c0;
528 border-right: 1px solid #c0c0c0;
528 border-right: 1px solid #c0c0c0;
529 text-align: center;
529 text-align: center;
530 overflow: hidden;
530 overflow: hidden;
531 }
531 }
532
532
533 .task {
533 .task {
534 position: absolute;
534 position: absolute;
535 height:8px;
535 height:8px;
536 font-size:0.8em;
536 font-size:0.8em;
537 color:#888;
537 color:#888;
538 padding:0;
538 padding:0;
539 margin:0;
539 margin:0;
540 line-height:0.8em;
540 line-height:0.8em;
541 }
541 }
542
542
543 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
543 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
544 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
544 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
545 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
545 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
546 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
546 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
547
547
548 /***** Tooltips ******/
548 /***** Tooltips ******/
549 .tooltip{position:relative;z-index:24;}
549 .tooltip{position:relative;z-index:24;}
550 .tooltip:hover{z-index:25;color:#000;}
550 .tooltip:hover{z-index:25;color:#000;}
551 .tooltip span.tip{display: none; text-align:left;}
551 .tooltip span.tip{display: none; text-align:left;}
552
552
553 div.tooltip:hover span.tip{
553 div.tooltip:hover span.tip{
554 display:block;
554 display:block;
555 position:absolute;
555 position:absolute;
556 top:12px; left:24px; width:270px;
556 top:12px; left:24px; width:270px;
557 border:1px solid #555;
557 border:1px solid #555;
558 background-color:#fff;
558 background-color:#fff;
559 padding: 4px;
559 padding: 4px;
560 font-size: 0.8em;
560 font-size: 0.8em;
561 color:#505050;
561 color:#505050;
562 }
562 }
563
563
564 /***** CSS FORM ******/
564 /***** CSS FORM ******/
565 .tabular p{
565 .tabular p{
566 margin: 0;
566 margin: 0;
567 padding: 5px 0 8px 0;
567 padding: 5px 0 8px 0;
568 padding-left: 180px; /*width of left column containing the label elements*/
568 padding-left: 180px; /*width of left column containing the label elements*/
569 height: 1%;
569 height: 1%;
570 clear:both;
570 clear:both;
571 }
571 }
572
572
573 .tabular label{
573 .tabular label{
574 font-weight: bold;
574 font-weight: bold;
575 float: left;
575 float: left;
576 margin-left: -180px; /*width of left column*/
576 margin-left: -180px; /*width of left column*/
577 margin-bottom: 10px;
577 margin-bottom: 10px;
578 width: 175px; /*width of labels. Should be smaller than left column to create some right
578 width: 175px; /*width of labels. Should be smaller than left column to create some right
579 margin*/
579 margin*/
580 }
580 }
581
581
582 .error {
582 .error {
583 color: #cc0000;
583 color: #cc0000;
584 }
584 }
585
585
586 #settings .tabular p{ padding-left: 300px; }
586 #settings .tabular p{ padding-left: 300px; }
587 #settings .tabular label{ margin-left: -300px; width: 295px; }
587 #settings .tabular label{ margin-left: -300px; width: 295px; }
588
588
589 /*.threepxfix class below:
589 /*.threepxfix class below:
590 Targets IE6- ONLY. Adds 3 pixel indent for multi-line form contents.
590 Targets IE6- ONLY. Adds 3 pixel indent for multi-line form contents.
591 to account for 3 pixel bug: http://www.positioniseverything.net/explorer/threepxtest.html
591 to account for 3 pixel bug: http://www.positioniseverything.net/explorer/threepxtest.html
592 */
592 */
593
593
594 * html .threepxfix{
594 * html .threepxfix{
595 margin-left: 3px;
595 margin-left: 3px;
596 }
596 }
597
597
598 /***** Wiki sections ****/
598 /***** Wiki sections ****/
599 #content div.wiki { font-size: 110%}
599 #content div.wiki { font-size: 110%}
600
600
601 #content div.wiki h2, div.wiki h3 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; color:#606060; }
601 #content div.wiki h2, div.wiki h3 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; color:#606060; }
602 #content div.wiki h2 { font-size: 1.4em;}
602 #content div.wiki h2 { font-size: 1.4em;}
603 #content div.wiki h3 { font-size: 1.2em;}
603 #content div.wiki h3 { font-size: 1.2em;}
604
604
605 div.wiki table {
605 div.wiki table {
606 border: 1px solid #505050;
606 border: 1px solid #505050;
607 border-collapse: collapse;
607 border-collapse: collapse;
608 }
608 }
609
609
610 div.wiki table, div.wiki td, div.wiki th {
610 div.wiki table, div.wiki td, div.wiki th {
611 border: 1px solid #bbb;
611 border: 1px solid #bbb;
612 padding: 4px;
612 padding: 4px;
613 }
613 }
614
614
615 div.wiki a {
615 div.wiki a {
616 background-position: 0% 60%;
616 background-position: 0% 60%;
617 background-repeat: no-repeat;
617 background-repeat: no-repeat;
618 padding-left: 14px;
618 padding-left: 12px;
619 background-image: url(../images/external.png);
619 background-image: url(../images/external.png);
620 }
620 }
621
621
622 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset, div.wiki a.email {
622 div.wiki a.wiki-page, div.wiki a.issue, div.wiki a.changeset, div.wiki a.email {
623 padding-left: 0;
623 padding-left: 0;
624 background-image: none;
624 background-image: none;
625 }
625 }
626
626
627 div.wiki a.new {
628 color: #b73535;
629 }
630
627 div.wiki code {
631 div.wiki code {
628 font-size: 1.2em;
632 font-size: 1.2em;
629 }
633 }
630
634
631 div.wiki img {
635 div.wiki img {
632 margin: 6px;
636 margin: 6px;
633 }
637 }
634
638
635 div.wiki pre {
639 div.wiki pre {
636 margin: 1em 1em 1em 1.6em;
640 margin: 1em 1em 1em 1.6em;
637 padding: 2px;
641 padding: 2px;
638 background-color: #fafafa;
642 background-color: #fafafa;
639 border: 1px solid #dadada;
643 border: 1px solid #dadada;
640 }
644 }
641
645
642 .diff_out{
646 .diff_out{
643 background: #fcc;
647 background: #fcc;
644 }
648 }
645
649
646 .diff_in{
650 .diff_in{
647 background: #cfc;
651 background: #cfc;
648 }
652 }
649
653
650 #preview .preview { background: #fafbfc url(../images/draft.png); }
654 #preview .preview { background: #fafbfc url(../images/draft.png); }
651
655
652 #ajax-indicator {
656 #ajax-indicator {
653 position: absolute; /* fixed not supported by IE */
657 position: absolute; /* fixed not supported by IE */
654 background-color:#eee;
658 background-color:#eee;
655 border: 1px solid #bbb;
659 border: 1px solid #bbb;
656 top:35%;
660 top:35%;
657 left:40%;
661 left:40%;
658 width:20%;
662 width:20%;
659 font-weight:bold;
663 font-weight:bold;
660 text-align:center;
664 text-align:center;
661 padding:0.6em;
665 padding:0.6em;
662 z-index:100;
666 z-index:100;
663 filter:alpha(opacity=50);
667 filter:alpha(opacity=50);
664 -moz-opacity:0.5;
668 -moz-opacity:0.5;
665 opacity: 0.5;
669 opacity: 0.5;
666 -khtml-opacity: 0.5;
670 -khtml-opacity: 0.5;
667 }
671 }
668
672
669 html>body #ajax-indicator { position: fixed; }
673 html>body #ajax-indicator { position: fixed; }
670
674
671 #ajax-indicator span {
675 #ajax-indicator span {
672 background-position: 0% 40%;
676 background-position: 0% 40%;
673 background-repeat: no-repeat;
677 background-repeat: no-repeat;
674 background-image: url(../images/loading.gif);
678 background-image: url(../images/loading.gif);
675 padding-left: 26px;
679 padding-left: 26px;
676 vertical-align: bottom;
680 vertical-align: bottom;
677 }
681 }
678
682
679 /***** Flash & error messages ****/
683 /***** Flash & error messages ****/
680 #flash div, #errorExplanation {
684 #flash div, #errorExplanation {
681 padding: 4px 4px 4px 30px;
685 padding: 4px 4px 4px 30px;
682 margin-bottom: 16px;
686 margin-bottom: 16px;
683 font-size: 1.1em;
687 font-size: 1.1em;
684 border: 2px solid;
688 border: 2px solid;
685 }
689 }
686
690
687 #flash div.error, #errorExplanation {
691 #flash div.error, #errorExplanation {
688 background: url(../images/false.png) 8px 5px no-repeat;
692 background: url(../images/false.png) 8px 5px no-repeat;
689 background-color: #ffe3e3;
693 background-color: #ffe3e3;
690 border-color: #dd0000;
694 border-color: #dd0000;
691 color: #550000;
695 color: #550000;
692 }
696 }
693
697
694 #flash div.notice {
698 #flash div.notice {
695 background: url(../images/true.png) 8px 5px no-repeat;
699 background: url(../images/true.png) 8px 5px no-repeat;
696 background-color: #dfffdf;
700 background-color: #dfffdf;
697 border-color: #9fcf9f;
701 border-color: #9fcf9f;
698 color: #005f00;
702 color: #005f00;
699 }
703 }
700
704
701 #errorExplanation ul { margin-bottom: 0px; }
705 #errorExplanation ul { margin-bottom: 0px; }
702 #errorExplanation ul li { list-style: none; margin-left: -16px;}
706 #errorExplanation ul li { list-style: none; margin-left: -16px;}
@@ -1,66 +1,69
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 fixtures :projects
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues
23
24
24 def setup
25 def setup
25 super
26 super
26 end
27 end
27
28
28 def test_auto_links
29 def test_auto_links
29 to_test = {
30 to_test = {
30 'http://foo.bar' => '<a href="http://foo.bar">http://foo.bar</a>',
31 'http://foo.bar' => '<a href="http://foo.bar">http://foo.bar</a>',
31 'www.foo.bar' => '<a href="http://www.foo.bar">www.foo.bar</a>',
32 'www.foo.bar' => '<a href="http://www.foo.bar">www.foo.bar</a>',
32 'http://foo.bar/page?p=1&t=z&s=' => '<a href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
33 'http://foo.bar/page?p=1&t=z&s=' => '<a href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
33 'http://foo.bar/page#125' => '<a href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
34 'http://foo.bar/page#125' => '<a href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
34 }
35 }
35 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
36 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
36 end
37 end
37
38
38 def test_auto_mailto
39 def test_auto_mailto
39 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
40 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
40 textilizable('test@foo.bar')
41 textilizable('test@foo.bar')
41 end
42 end
42
43
43 def test_textile_tags
44 def test_textile_tags
44 to_test = {
45 to_test = {
45 # inline images
46 # inline images
46 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
47 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
47 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
48 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
48 # textile links
49 # textile links
49 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar">link</a>',
50 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar">link</a>',
50 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title">link</a>'
51 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title">link</a>'
51 }
52 }
52 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
53 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
53 end
54 end
54
55
55 def test_redmine_links
56 def test_redmine_links
56 issue_link = link_to('#52', {:controller => 'issues', :action => 'show', :id => 52}, :class => 'issue')
57 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
57 changeset_link = link_to('r19', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 19}, :class => 'changeset')
58 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
59 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
60 :class => 'changeset', :title => 'My very first commit')
58
61
59 to_test = {
62 to_test = {
60 '#52, #52 and #52.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
63 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
61 'r19' => changeset_link
64 'r1' => changeset_link
62 }
65 }
63 @project = Project.find(1)
66 @project = Project.find(1)
64 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
67 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
65 end
68 end
66 end
69 end
General Comments 0
You need to be logged in to leave comments. Login now