Append .js to JS file
[gitorious:yousource.git] / app / helpers / application_helper.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4 #   Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
5 #   Copyright (C) 2008 August Lilleaas <augustlilleaas@gmail.com>
6 #   Copyright (C) 2008 David A. Cuadrado <krawek@gmail.com>
7 #   Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
8 #   Copyright (C) 2009 Fabio Akita <fabio.akita@gmail.com>
9 #   Copyright (C) 2009 Bill Marquette <bill.marquette@gmail.com>
10 #
11 #   This program is free software: you can redistribute it and/or modify
12 #   it under the terms of the GNU Affero General Public License as published by
13 #   the Free Software Foundation, either version 3 of the License, or
14 #   (at your option) any later version.
15 #
16 #   This program is distributed in the hope that it will be useful,
17 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
18 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 #   GNU Affero General Public License for more details.
20 #
21 #   You should have received a copy of the GNU Affero General Public License
22 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #++
24
25 # Methods added to this helper will be available to all templates in the application.
26 module ApplicationHelper
27   include TagsHelper
28   include UsersHelper
29   include BreadcrumbsHelper
30   include EventRenderingHelper
31   
32   def help_box(style = :side, icon = :help, options = {}, &block)
33     out = %Q{<div id="#{options.delete(:id)}" style="#{options.delete(:style)}"
34                   class="help-box #{style} #{icon} round-5">
35                <div class="icon #{icon}"></div>}
36     out << capture(&block)
37     out << "</div>"
38     concat(out)
39   end
40   
41   def pull_box(title, options = {}, &block)
42     css_class = options.delete(:class)
43     out = %Q{<div class="pull-box round-top-5 #{css_class}">}
44     out << %Q{<h3 class="round-top-5 pull-box-header">#{title}</h3>} if title
45     out << %Q{<div class="pull-box-content">}
46     out << capture(&block)
47     out << "</div></div>"
48     concat(out)
49   end
50   
51   def dialog_box(title, options = {}, &block)
52     css_class = options.delete(:class)
53     out = %Q{<div class="dialog-box #{css_class}">}
54     out << %Q{<h3 class="round-top-5 dialog-box-header">#{title}</h3>} if title
55     out << %Q{<div class="dialog-box-content">}
56     out << capture(&block)
57     out << "</div></div>"
58     concat(out)
59   end
60   
61   def markdown(text, options = [:smart])
62     renderer = MarkupRenderer.new(text, :markdown => options)
63     renderer.to_html
64   end
65
66   def render_markdown(text, *options)
67     # RDiscount < 1.4 doesn't support the :auto_link, use Rails' instead
68     auto_link = options.delete(:auto_link)
69     markdown_options = [:smart] + options
70     markdownized_text = markdown(text, markdown_options)
71     if auto_link
72       markdownized_text = auto_link(markdownized_text, :urls)
73     end
74     sanitize(markdownized_text)
75   end
76   
77   def feed_icon(url, alt_title = "Atom feed", size = :small)
78     link_to image_tag("silk/feed.png", :class => "feed_icon"), url,
79       :alt => alt_title, :title => alt_title
80   end
81   
82   def default_css_tag_sizes
83     %w(tag_size_1 tag_size_2 tag_size_3 tag_size_4)
84   end
85   
86   def linked_tag_list_as_sentence(tags)
87     tags.map do |tag|
88       link_to(h(tag.name), search_path(:q => "category:#{h(tag.name)}"))
89     end.to_sentence
90   end
91   
92   def build_notice_for(object)
93     out =  %Q{<div class="being_constructed round-10">}
94     out <<  %Q{<div class="being_constructed_content round-10">}
95     out << %Q{  <p>#{I18n.t( "application_helper.notice_for").call(object.class.name.humanize.downcase)}</p>}
96     out << %Q{  <p class="spin">#{image_tag("spinner.gif")}</p>}
97     out << %Q{  <p class="hint">If this message persist beyond what's reasonable, feel free to #{link_to("contact us", contact_path)}</p>}
98     out << %Q{</div></div>}
99     out
100   end
101   
102   def render_if_ready(object)
103     if object.respond_to?(:ready?) && object.ready?
104       yield
105     else
106       concat(build_notice_for(object))
107     end
108   end  
109   
110   def selected_if_current_page(url_options, slack = false)
111     if slack
112       if controller.request.request_uri.index(CGI.escapeHTML(url_for(url_options))) == 0
113         "selected"
114       end
115     else
116       "selected" if current_page?(url_options)
117     end
118   end
119   
120   def submenu_selected_class_if_current?(section)
121     case section
122     when :overview
123      if %w[projects].include?(controller.controller_name )
124        return "selected"
125      end
126     when :repositories
127       if %w[repositories trees logs commits comitters comments merge_requests 
128             blobs committers].include?(controller.controller_name )
129         return "selected"
130       end
131     when :pages
132       if %w[pages].include?(controller.controller_name )
133         return "selected"
134       end
135     end
136   end
137   
138   def link_to_with_selected(name, options = {}, html_options = nil)
139     html_options = current_page?(options) ? {:class => "selected"} : nil
140     link_to(name, options = {}, html_options)
141   end
142   
143   def syntax_themes_css
144     out = []
145     if @load_syntax_themes
146       # %w[ active4d all_hallows_eve amy blackboard brilliance_black brilliance_dull 
147       #     cobalt dawn eiffel espresso_libre idle iplastic lazy mac_classic 
148       #     magicwb_amiga pastels_on_dark slush_poppies spacecadet sunburst 
149       #     twilight zenburnesque 
150       # ].each do |syntax|
151       #   out << stylesheet_link_tag("syntax_themes/#{syntax}")
152       # end
153       return stylesheet_link_tag("syntax_themes/idle")
154     end
155     out.join("\n")
156   end
157   
158   def base_url(full_url)
159     URI.parse(full_url).host
160   end
161   
162   def gravatar_url_for(email, options = {})
163     options.reverse_merge!(:default => "images/default_face.gif")
164     port_string = [443, 80].include?(request.port) ? "" : ":#{request.port}"
165     "http://www.gravatar.com/avatar.php?gravatar_id=" +
166     (email.nil? ? "" : Digest::MD5.hexdigest(email)) + "&amp;default=" +
167       u("http://#{GitoriousConfig['gitorious_host']}#{port_string}" +
168       "/#{options.delete(:default)}") +
169     options.map { |k,v| "&amp;#{k}=#{v}" }.join
170   end
171   
172   # For a User object, return either his/her avatar or the gravatar for her email address
173   # Options
174   # - Pass on :size for the height+width of the image in pixels
175   # - Pass on :version for a named version/style of the avatar
176   def avatar(user, options={})
177     if user.avatar?
178       avatar_style = options.delete(:version) || :thumb
179       image_options = { :alt => 'avatar'}.merge(:width => options[:size], :height => options[:size])
180       image_tag(user.avatar.url(avatar_style), image_options)
181     else
182       gravatar(user.email, options)
183     end
184   end
185   
186   # Returns an avatar from an email address (for instance from a commit) where we don't have an actual User object
187   def avatar_from_email(email, options={})
188     return if email.blank?
189     avatar_style = options.delete(:version) || :thumb
190     image = User.find_avatar_for_email(email, avatar_style)
191     if image == :nil
192       gravatar(email, options)
193     else
194       image_options = { :alt => 'avatar'}.merge(:width => options[:size], :height => options[:size])
195       image_tag(image, image_options)
196     end
197   end
198   
199   def gravatar(email, options = {})
200     size = options[:size]
201     image_options = { :alt => "avatar" }
202     if size
203       image_options.merge!(:width => size, :height => size)
204     end
205     image_tag(gravatar_url_for(email, options), image_options)
206   end
207   
208   def gravatar_frame(email, options = {})
209     extra_css_class = options[:style] ? " gravatar_#{options[:style]}" : ""
210     %{<div class="gravatar#{extra_css_class}">#{gravatar(email, options)}</div>}
211   end
212   
213   def flashes
214     flash.map do |type, content| 
215       content_tag(:div, content_tag(:p, content), :class => "flash_message #{type}")
216     end.join("\n")
217   end
218   
219   def commit_graph_tag(repository, ref = "master")
220   end
221   
222   def commit_graph_by_author_tag(repository, ref = "master")    
223   end
224   
225   def action_and_body_for_event(event)
226     target = event.target
227     if target.nil?
228       return ["", "", ""]
229     end
230     # These are defined in event_rendering_helper.rb:
231     action, body, category = self.send("render_event_#{Action::css_class(event.action)}", event)
232     
233     body = sanitize(body, :tags => %w[a em i strong b])
234     [action, body, category]
235   end
236   
237   def link_to_remote_if(condition, name, options, html_options = {})
238     if condition
239       link_to_remote(name, options, html_options)
240     else
241       content_tag(:span, name)
242     end
243   end
244   
245   def sidebar_content?
246     !@content_for_sidebar.blank?
247   end
248   
249   def render_readme(repository)
250     possibilities = []
251     repository.git.git.ls_tree({:name_only => true}, "master").each do |line|
252       possibilities << line[0, line.length-1] if line =~ /README.*/
253     end
254     
255     return "" if possibilities.empty?
256     text = repository.git.git.show({}, "master:#{possibilities.first}")
257     markdown(text) rescue simple_format(sanitize(text))
258   end
259   
260   def render_markdown_help
261     render :partial => '/site/markdown_help'
262   end
263   
264   def file_path(repository, filename, head = "master")
265     project_repository_blob_path(repository.project, repository, branch_with_tree(head, filename))
266   end
267   
268   def link_to_help_toggle(dom_id, style = :image)
269     if style == :image
270       link_to_function(image_tag("help_grey.png", {
271         :alt => t("application_helper.more_info")
272       }), "$('##{dom_id}').toggle()", :class => "more_info")
273     else
274       %Q{<span class="hint">(} +
275       link_to_function("?", "$('##{dom_id}').toggle()", :class => "more_info") +
276       ")</span>"
277     end
278     
279   end
280   
281   FILE_EXTN_MAPPINGS = {
282     '.cpp' => 'cplusplus-file', 
283     '.c' => 'c-file',
284     '.h' => 'header-file',
285     '.java' => 'java-file',
286     '.sh' => 'exec-file',
287     '.exe'  => 'exec-file',
288     '.rb' => 'ruby-file',
289     '.png' => 'image-file',
290     '.jpg' => 'image-file', 
291     '.gif' => 'image-file',
292     'jpeg' => 'image-file',
293     '.zip' => 'compressed-file',
294     '.gz' => 'compressed-file'}
295   
296   def class_for_filename(filename)
297     return FILE_EXTN_MAPPINGS[File.extname(filename)] || 'file'
298   end
299   
300   def render_download_links(project, repository, head, options={})
301     links = []
302     exceptions = Array(options[:except])
303     unless exceptions.include?(:source_tree)
304       links << content_tag(:li, link_to("View source tree for #{desplat_path(head)}", 
305                   tree_path(head)), :class => "tree")
306     end
307
308     head = desplat_path(head) if head.is_a?(Array)
309     
310     if head =~ /^[a-z0-9]{40}$/ # it looks like a SHA1
311       head = head[0..7]
312     end
313     
314     {
315       'tar.gz' => 'tar',
316       # 'zip' => 'zip',
317     }.each do |extension, url_key|
318       archive_path = self.send("project_repository_archive_#{url_key}_path", project, repository, head)
319       link_html = link_to("Download #{head} as #{extension}", archive_path, 
320                                   :onclick => "Gitorious.DownloadChecker.checkURL('#{archive_path}?format=js', 'archive-box-#{head}');return false",
321                                   :class => "download-link")
322       link_callback_box = content_tag(:div, "", :class => "archive-download-box round-5 shadow-2", 
323         :id => "archive-box-#{head}", :style => "display:none;")
324       links << content_tag(:li, link_html+link_callback_box, :class => extension.split('.').last)
325     end
326     
327     if options.delete(:only_list_items)
328       links.join("\n")
329     else
330       css_classes = options[:class] || "meta"
331       content_tag(:ul, links.join("\n"), :class => "links #{css_classes}")
332     end
333   end
334   
335   def paragraphs_with_more(text, identifier)
336     return if text.blank?
337     first, rest = text.split("</p>", 2)
338     if rest.blank?
339       first + "</p>"
340     else
341       %Q{#{first} 
342         <a href="#more"
343            onclick="$('#description-rest-#{identifier}').toggle(); $(this).hide()">more&hellip;</a></p>
344         <div id="description-rest-#{identifier}" style="display:none;">#{rest}</div>}
345     end
346   end
347   
348   def markdown_hint
349     t("views.common.format_using_markdown", 
350       :markdown => %(<a href="http://daringfireball.net/projects/markdown/">Markdown</a>))
351   end
352   
353   def current_site
354     @controller.current_site
355   end
356   
357   def new_polymorphic_comment_path(parent, comment)
358     if parent
359       repo_owner_path(@repository, [@project, @repository, parent, comment])
360     else
361       repo_owner_path(@repository, [@project, @repository, comment])
362     end
363   end
364   
365   def force_utf8(str)
366     if str.respond_to?(:force_encoding)
367       str.force_encoding("UTF-8")
368       if str.valid_encoding?
369         str
370       else
371         str.encode("binary", :invalid => :replace, :undef => :replace).encode("utf-8")
372       end
373     else
374       str.mb_chars
375     end
376       
377   end
378   
379   # Creates a CSS styled <button>.
380   #
381   #  <%= styled_button :big, "Create user" %>
382   #  <%= styled_button :medium, "Do something!", :class => "foo", :id => "bar" %>
383   def styled_button(size_identifier, label, options = {})
384     options.reverse_merge!(:type => "submit", :class => size_identifier.to_s)
385     content_tag(:button, %{<span>#{label}</span>}, options)
386   end
387   
388   # Similar to styled_button, but creates a link_to <a>, not a <button>.
389   #
390   #  <%= button_link :big, "Sign up", new_user_path %>
391   def button_link(size_identifier, label, url, options = {})
392     options[:class] = "#{size_identifier} button_link"
393     link_to(%{<span>#{label}</span>}, url, options)
394   end
395   
396   # Array => HTML list. The option hash is applied to the <ul> tag.
397   #
398   #  <%= list(items) {|i| i.title } %>
399   #  <%= list(items, :class => "foo") {|i| link_to i, foo_path }
400   def list(items, options = {})
401     list_items = items.map {|i| %{<li>#{block_given? ? yield(i) : i}</li>} }.join("\n")
402     content_tag(:ul, list_items, options)
403   end
404   
405   def summary_box(title, content, image)
406     %{
407       <div class="summary_box">
408         <div class="summary_box_image">
409           #{image}
410         </div>
411         
412         <div class="summary_box_content">
413           <strong>#{title}</strong>
414           #{content}
415         </div>
416         
417         <div class="clear"></div>
418       </div>
419     }
420   end
421   
422   def project_summary_box(project)
423     summary_box link_to(project.title, project),
424       truncate(project.descriptions_first_paragraph, 80),
425       glossy_homepage_avatar(default_avatar)
426   end
427   
428   def team_summary_box(team)
429     text = list([
430       "Created: #{team.created_at.strftime("%B #{team.created_at.strftime("%d").to_i.ordinalize} %Y")}",
431       "Total activities: #{team.event_count}"
432     ], :class => "simple")
433     
434     summary_box link_to(team.name, group_path(team)),
435       text,
436       glossy_homepage_avatar(team.avatar? ? image_tag(team.avatar.url(:thumb), :width => 30, :height => 30) : default_avatar)
437       
438   end
439   
440   def user_summary_box(user)
441     text = text = list([
442       "Projects: #{user.projects.count}",
443       "Total activities: #{user.events.count}"
444     ], :class => "simple")
445     
446     summary_box link_to(user.login, user),
447       text,
448       glossy_homepage_avatar_for_user(user)
449   end
450   
451   def glossy_homepage_avatar(avatar)
452     content_tag(:div, avatar + "<span></span>", :class => "glossy_avatar_wrapper")
453   end
454   
455   def glossy_homepage_avatar_for_user(user)
456     glossy_homepage_avatar(avatar(user, :size => 30, :default => "images/icon_default.png"))
457   end
458   
459   def default_avatar
460     image_tag("icon_default.png", :width => 30, :height => 30)
461   end
462
463   def secure_login_url
464     if SslRequirement.disable_ssl_check?
465       sessions_path
466     else
467       sessions_url(:protocol => "https", :host => SslRequirement.ssl_host)
468     end
469   end
470
471   def comment_applies_to_merge_request?(parent)
472     MergeRequest === parent && (logged_in? && parent.resolvable_by?(current_user))
473   end
474
475   def statuses_for_merge_request_for_select(merge_request)
476     merge_request.target_repository.project.merge_request_statuses.map do |status|
477       if status.description.blank?
478         [h(status.name), h(status.name)]
479       else
480         [h("#{status.name} - #{status.description}"), h(status.name)]
481       end
482     end
483   end
484
485   # The javascripts to be included in all layouts
486   def include_javascripts
487     javascript_include_tag "jquery.core", "jquery.autocomplete", "jquery.cookie",
488       "color_picker", "ui.core","ui.selectable",
489       "core_extensions", "jquery.gitorious_extensions", "merge_requests", "diff_browser",
490       "messages", "application", :cache => true
491   end
492
493   # inserts a <wbr> tag somewhere in the middle of +str+
494   def wbr_middle(str)
495     half_size = str.length / 2
496     str.to_s[0..half_size-1] + "<wbr />" + str[half_size..-1]
497   end
498 end