##// END OF EJS Templates
Support for Javascript in Themes (#2803)....
Jean-Philippe Lang -
r4444:523febf9c1b3
parent child
Show More
@@ -0,0 +1,60
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.expand_path('../../../../test_helper', __FILE__)
19
20 class ThemesTest < ActionController::IntegrationTest
21 fixtures :all
22
23 def setup
24 @theme = Redmine::Themes.themes.last
25 Setting.ui_theme = @theme.id
26 end
27
28 def teardown
29 Setting.ui_theme = ''
30 end
31
32 def test_application_css
33 get '/'
34
35 assert_response :success
36 assert_tag :tag => 'link',
37 :attributes => {:href => %r{^/themes/#{@theme.dir}/stylesheets/application.css}}
38 end
39
40 def test_without_theme_js
41 get '/'
42
43 assert_response :success
44 assert_no_tag :tag => 'script',
45 :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}}
46 end
47
48 def test_with_theme_js
49 # Simulates a theme.js
50 @theme.javascripts << 'theme'
51 get '/'
52
53 assert_response :success
54 assert_tag :tag => 'script',
55 :attributes => {:src => %r{^/themes/#{@theme.dir}/javascripts/theme.js}}
56
57 ensure
58 @theme.javascripts.delete 'theme'
59 end
60 end
@@ -1,78 +1,79
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
3 <head>
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
4 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5 <title><%=h html_title %></title>
5 <title><%=h html_title %></title>
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
6 <meta name="description" content="<%= Redmine::Info.app_name %>" />
7 <meta name="keywords" content="issue,bug,tracker" />
7 <meta name="keywords" content="issue,bug,tracker" />
8 <%= favicon %>
8 <%= favicon %>
9 <%= stylesheet_link_tag 'application', :media => 'all' %>
9 <%= stylesheet_link_tag 'application', :media => 'all' %>
10 <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %>
10 <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %>
11 <%= javascript_include_tag :defaults %>
11 <%= javascript_include_tag :defaults %>
12 <%= heads_for_theme %>
12 <%= heads_for_wiki_formatter %>
13 <%= heads_for_wiki_formatter %>
13 <!--[if IE]>
14 <!--[if IE]>
14 <style type="text/css">
15 <style type="text/css">
15 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
16 * html body{ width: expression( document.documentElement.clientWidth < 900 ? '900px' : '100%' ); }
16 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
17 body {behavior: url(<%= stylesheet_path "csshover.htc" %>);}
17 </style>
18 </style>
18 <![endif]-->
19 <![endif]-->
19 <%= call_hook :view_layouts_base_html_head %>
20 <%= call_hook :view_layouts_base_html_head %>
20 <!-- page specific tags -->
21 <!-- page specific tags -->
21 <%= yield :header_tags -%>
22 <%= yield :header_tags -%>
22 </head>
23 </head>
23 <body class="<%= body_css_classes %>">
24 <body class="<%= body_css_classes %>">
24 <div id="wrapper">
25 <div id="wrapper">
25 <div id="wrapper2">
26 <div id="wrapper2">
26 <div id="top-menu">
27 <div id="top-menu">
27 <div id="account">
28 <div id="account">
28 <%= render_menu :account_menu -%>
29 <%= render_menu :account_menu -%>
29 </div>
30 </div>
30 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
31 <%= content_tag('div', "#{l(:label_logged_as)} #{link_to_user(User.current, :format => :username)}", :id => 'loggedas') if User.current.logged? %>
31 <%= render_menu :top_menu -%>
32 <%= render_menu :top_menu -%>
32 </div>
33 </div>
33
34
34 <div id="header">
35 <div id="header">
35 <div id="quick-search">
36 <div id="quick-search">
36 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
37 <% form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %>
37 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
38 <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
38 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
39 <%= link_to l(:label_search), {:controller => 'search', :action => 'index', :id => @project}, :accesskey => accesskey(:search) %>:
39 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
40 <%= text_field_tag 'q', @question, :size => 20, :class => 'small', :accesskey => accesskey(:quick_search) %>
40 <% end %>
41 <% end %>
41 <%= render_project_jump_box %>
42 <%= render_project_jump_box %>
42 </div>
43 </div>
43
44
44 <h1><%= page_header_title %></h1>
45 <h1><%= page_header_title %></h1>
45
46
46 <% if display_main_menu?(@project) %>
47 <% if display_main_menu?(@project) %>
47 <div id="main-menu">
48 <div id="main-menu">
48 <%= render_main_menu(@project) %>
49 <%= render_main_menu(@project) %>
49 </div>
50 </div>
50 <% end %>
51 <% end %>
51 </div>
52 </div>
52
53
53 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
54 <%= tag('div', {:id => 'main', :class => (has_content?(:sidebar) ? '' : 'nosidebar')}, true) %>
54 <div id="sidebar">
55 <div id="sidebar">
55 <%= yield :sidebar %>
56 <%= yield :sidebar %>
56 <%= call_hook :view_layouts_base_sidebar %>
57 <%= call_hook :view_layouts_base_sidebar %>
57 </div>
58 </div>
58
59
59 <div id="content">
60 <div id="content">
60 <%= render_flash_messages %>
61 <%= render_flash_messages %>
61 <%= yield %>
62 <%= yield %>
62 <%= call_hook :view_layouts_base_content %>
63 <%= call_hook :view_layouts_base_content %>
63 <div style="clear:both;"></div>
64 <div style="clear:both;"></div>
64 </div>
65 </div>
65 </div>
66 </div>
66
67
67 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
68 <div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>
68
69
69 <div id="footer">
70 <div id="footer">
70 <div class="bgl"><div class="bgr">
71 <div class="bgl"><div class="bgr">
71 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
72 Powered by <%= link_to Redmine::Info.app_name, Redmine::Info.url %> &copy; 2006-2010 Jean-Philippe Lang
72 </div></div>
73 </div></div>
73 </div>
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 <%= call_hook :view_layouts_base_body_bottom %>
77 <%= call_hook :view_layouts_base_body_bottom %>
77 </body>
78 </body>
78 </html>
79 </html>
@@ -1,85 +1,125
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 Redmine
18 module Redmine
19 module Themes
19 module Themes
20
20
21 # Return an array of installed themes
21 # Return an array of installed themes
22 def self.themes
22 def self.themes
23 @@installed_themes ||= scan_themes
23 @@installed_themes ||= scan_themes
24 end
24 end
25
25
26 # Rescan themes directory
26 # Rescan themes directory
27 def self.rescan
27 def self.rescan
28 @@installed_themes = scan_themes
28 @@installed_themes = scan_themes
29 end
29 end
30
30
31 # Return theme for given id, or nil if it's not found
31 # Return theme for given id, or nil if it's not found
32 def self.theme(id, options={})
32 def self.theme(id, options={})
33 found = themes.find {|t| t.id == id}
33 found = themes.find {|t| t.id == id}
34 if found.nil? && options[:rescan] != false
34 if found.nil? && options[:rescan] != false
35 rescan
35 rescan
36 found = theme(id, :rescan => false)
36 found = theme(id, :rescan => false)
37 end
37 end
38 found
38 found
39 end
39 end
40
40
41 # Class used to represent a theme
41 # Class used to represent a theme
42 class Theme
42 class Theme
43 attr_reader :name, :dir, :stylesheets
43 attr_reader :path, :name, :dir
44
44
45 def initialize(path)
45 def initialize(path)
46 @path = path
46 @dir = File.basename(path)
47 @dir = File.basename(path)
47 @name = @dir.humanize
48 @name = @dir.humanize
48 @stylesheets = Dir.glob("#{path}/stylesheets/*.css").collect {|f| File.basename(f).gsub(/\.css$/, '')}
49 @stylesheets = nil
50 @javascripts = nil
49 end
51 end
50
52
51 # Directory name used as the theme id
53 # Directory name used as the theme id
52 def id; dir end
54 def id; dir end
53
55
54 def ==(theme)
56 def ==(theme)
55 theme.is_a?(Theme) && theme.dir == dir
57 theme.is_a?(Theme) && theme.dir == dir
56 end
58 end
57
59
58 def <=>(theme)
60 def <=>(theme)
59 name <=> theme.name
61 name <=> theme.name
60 end
62 end
63
64 def stylesheets
65 @stylesheets ||= assets("stylesheets", "css")
66 end
67
68 def javascripts
69 @javascripts ||= assets("javascripts", "js")
70 end
71
72 def stylesheet_path(source)
73 "/themes/#{dir}/stylesheets/#{source}"
74 end
75
76 def javascript_path(source)
77 "/themes/#{dir}/javascripts/#{source}"
78 end
79
80 private
81
82 def assets(dir, ext)
83 Dir.glob("#{path}/#{dir}/*.#{ext}").collect {|f| File.basename(f).gsub(/\.#{ext}$/, '')}
84 end
61 end
85 end
62
86
63 private
87 private
64
88
65 def self.scan_themes
89 def self.scan_themes
66 dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f|
90 dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f|
67 # A theme should at least override application.css
91 # A theme should at least override application.css
68 File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
92 File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
69 end
93 end
70 dirs.collect {|dir| Theme.new(dir)}.sort
94 dirs.collect {|dir| Theme.new(dir)}.sort
71 end
95 end
72 end
96 end
73 end
97 end
74
98
75 module ApplicationHelper
99 module ApplicationHelper
100 def current_theme
101 unless instance_variable_defined?(:@current_theme)
102 @current_theme = Redmine::Themes.theme(Setting.ui_theme)
103 end
104 @current_theme
105 end
106
76 def stylesheet_path(source)
107 def stylesheet_path(source)
77 @current_theme ||= Redmine::Themes.theme(Setting.ui_theme)
108 if current_theme && current_theme.stylesheets.include?(source)
78 super((@current_theme && @current_theme.stylesheets.include?(source)) ?
109 super current_theme.stylesheet_path(source)
79 "/themes/#{@current_theme.dir}/stylesheets/#{source}" : source)
110 else
111 super
112 end
80 end
113 end
81
114
82 def path_to_stylesheet(source)
115 def path_to_stylesheet(source)
83 stylesheet_path source
116 stylesheet_path source
84 end
117 end
118
119 # Returns the header tags for the current theme
120 def heads_for_theme
121 if current_theme && current_theme.javascripts.include?('theme')
122 javascript_include_tag current_theme.javascript_path('theme')
123 end
124 end
85 end
125 end
General Comments 0
You need to be logged in to leave comments. Login now