Commit dad628a9875934c8d18f27efb6e4f59b94237aa0

Authored by Braulio Bhavamitra
1 parent 4ef7f762

Refactor search ordering, boost, indexing and CSS

app/controllers/public/search_controller.rb
1 class SearchController < PublicController 1 class SearchController < PublicController
2 2
3 - MAP_SEARCH_LIMIT = 2000  
4 - LIST_SEARCH_LIMIT = 20  
5 - BLOCKS_SEARCH_LIMIT = 18  
6 - MULTIPLE_SEARCH_LIMIT = 8  
7 -  
8 helper TagsHelper 3 helper TagsHelper
9 include SearchHelper 4 include SearchHelper
10 include ActionView::Helpers::NumberHelper 5 include ActionView::Helpers::NumberHelper
@@ -33,7 +28,6 @@ class SearchController &lt; PublicController @@ -33,7 +28,6 @@ class SearchController &lt; PublicController
33 full_text_search ['public:true'] 28 full_text_search ['public:true']
34 else 29 else
35 @results[@asset] = @environment.articles.public.send(@filter).paginate(paginate_options) 30 @results[@asset] = @environment.articles.public.send(@filter).paginate(paginate_options)
36 - facets = {}  
37 end 31 end
38 end 32 end
39 33
@@ -46,7 +40,6 @@ class SearchController &lt; PublicController @@ -46,7 +40,6 @@ class SearchController &lt; PublicController
46 full_text_search ['public:true'] 40 full_text_search ['public:true']
47 else 41 else
48 @results[@asset] = @environment.people.visible.send(@filter).paginate(paginate_options) 42 @results[@asset] = @environment.people.visible.send(@filter).paginate(paginate_options)
49 - @facets = {}  
50 end 43 end
51 end 44 end
52 45
@@ -54,8 +47,19 @@ class SearchController &lt; PublicController @@ -54,8 +47,19 @@ class SearchController &lt; PublicController
54 if !@empty_query 47 if !@empty_query
55 full_text_search ['public:true'] 48 full_text_search ['public:true']
56 else 49 else
57 - @results[@asset] = @environment.products.send(@filter).paginate(paginate_options)  
58 - @facets = {} 50 + @one_page = true
  51 + @geosearch = logged_in? && current_user.person.lat && current_user.person.lng
  52 +
  53 + extra_limit = LIST_SEARCH_LIMIT*5
  54 + sql_options = {:limit => LIST_SEARCH_LIMIT, :order => 'random()'}
  55 + if @geosearch
  56 + full_text_search ['public:true', "{!geofilt}"], :sql_options => sql_options, :extra_limit => extra_limit,
  57 + :alternate_query => "{!boost b=recip(geodist(),#{1/DistBoost},1,1)}",
  58 + :radius => DistFilt, :latitude => current_user.person.lat, :longitude => current_user.person.lng
  59 + else
  60 + full_text_search ['public:true'], :sql_options => sql_options, :extra_limit => extra_limit,
  61 + :boost_functions => ['recip(ms(NOW/HOUR,updated_at),1.3e-10,1,1)']
  62 + end
59 end 63 end
60 end 64 end
61 65
@@ -77,40 +81,25 @@ class SearchController &lt; PublicController @@ -77,40 +81,25 @@ class SearchController &lt; PublicController
77 end 81 end
78 82
79 def events 83 def events
80 - @category_id = @category ? @category.id : nil  
81 -  
82 year = (params[:year] ? params[:year].to_i : Date.today.year) 84 year = (params[:year] ? params[:year].to_i : Date.today.year)
83 month = (params[:month] ? params[:month].to_i : Date.today.month) 85 month = (params[:month] ? params[:month].to_i : Date.today.month)
84 day = (params[:day] ? params[:day].to_i : Date.today.day) 86 day = (params[:day] ? params[:day].to_i : Date.today.day)
85 - date = Date.new(year, month, day) 87 + date = build_date(params[:year], params[:month], params[:day])
86 date_range = (date - 1.month)..(date + 1.month).at_end_of_month 88 date_range = (date - 1.month)..(date + 1.month).at_end_of_month
87 89
88 - if @query.blank?  
89 - # Ignore pagination for asset events  
90 - if date_range  
91 - @results[@asset] = Event.send('find', :all,  
92 - :conditions => [  
93 - 'start_date BETWEEN :start_day AND :end_day OR end_date BETWEEN :start_day AND :end_day',  
94 - {:start_day => date_range.first, :end_day => date_range.last}  
95 - ])  
96 - else  
97 - @results[@asset] = Event.send('find', :all)  
98 - end  
99 - else  
100 - full_text_search  
101 - end  
102 -  
103 @selected_day = nil 90 @selected_day = nil
104 @events_of_the_day = [] 91 @events_of_the_day = []
105 - date = build_date(params[:year], params[:month], params[:day])  
106 -  
107 if params[:day] || !params[:year] && !params[:month] 92 if params[:day] || !params[:year] && !params[:month]
108 @selected_day = date 93 @selected_day = date
109 - if @category_id and Category.exists?(@category_id)  
110 - @events_of_the_day = environment.events.by_day(@selected_day).in_category(Category.find(@category_id))  
111 - else  
112 - @events_of_the_day = environment.events.by_day(@selected_day)  
113 - end 94 + @events_of_the_day = @category ?
  95 + environment.events.by_day(@selected_day).in_category(Category.find(@category_id)) :
  96 + environment.events.by_day(@selected_day)
  97 + end
  98 +
  99 + if !@empty_query
  100 + full_text_search
  101 + else
  102 + @results[@asset] = date_range ? environment.events.by_range(date_range) : environment.events
114 end 103 end
115 104
116 events = @results[@asset] 105 events = @results[@asset]
@@ -135,17 +124,10 @@ class SearchController &lt; PublicController @@ -135,17 +124,10 @@ class SearchController &lt; PublicController
135 @asset = nil 124 @asset = nil
136 @facets = {} 125 @facets = {}
137 126
138 - if @results.keys.size == 1  
139 - specific_action = @results.keys.first  
140 - if respond_to?(specific_action)  
141 - @asset_name = getterm(@names[@results.keys.first])  
142 - send(specific_action)  
143 - render :action => specific_action  
144 - return  
145 - end  
146 - end 127 + render :action => @results.keys.first if @results.keys.size == 1
147 end 128 end
148 129
  130 + # keep old URLs workings
149 def assets 131 def assets
150 params[:action] = params[:asset].is_a?(Array) ? :index : params.delete(:asset) 132 params[:action] = params[:asset].is_a?(Array) ? :index : params.delete(:asset)
151 redirect_to params 133 redirect_to params
@@ -167,6 +149,7 @@ class SearchController &lt; PublicController @@ -167,6 +149,7 @@ class SearchController &lt; PublicController
167 ].each do |asset, name, filter| 149 ].each do |asset, name, filter|
168 @order << asset 150 @order << asset
169 @results[asset] = @category.send(filter, limit) 151 @results[asset] = @category.send(filter, limit)
  152 + raise "nao total #{asset}" unless @results[asset].respond_to?(:total_entries)
170 @names[asset] = name 153 @names[asset] = name
171 end 154 end
172 end 155 end
@@ -182,7 +165,8 @@ class SearchController &lt; PublicController @@ -182,7 +165,8 @@ class SearchController &lt; PublicController
182 @tag = params[:tag] 165 @tag = params[:tag]
183 @tag_cache_key = "tag_#{CGI.escape(@tag.to_s)}_env_#{environment.id.to_s}_page_#{params[:npage]}" 166 @tag_cache_key = "tag_#{CGI.escape(@tag.to_s)}_env_#{environment.id.to_s}_page_#{params[:npage]}"
184 if is_cache_expired?(@tag_cache_key) 167 if is_cache_expired?(@tag_cache_key)
185 - @tagged = environment.articles.find_tagged_with(@tag).paginate(:per_page => 10, :page => params[:npage]) 168 + @asset = :articles
  169 + @results[@asset] = environment.articles.find_tagged_with(@tag).paginate(paginate_options)
186 end 170 end
187 end 171 end
188 172
@@ -241,7 +225,7 @@ class SearchController &lt; PublicController @@ -241,7 +225,7 @@ class SearchController &lt; PublicController
241 'communities_more_recent' => _('More recent communities from network'), 225 'communities_more_recent' => _('More recent communities from network'),
242 'communities_more_active' => _('More active communities from network'), 226 'communities_more_active' => _('More active communities from network'),
243 'communities_more_popular' => _('More popular communities from network'), 227 'communities_more_popular' => _('More popular communities from network'),
244 - 'products_more_recent' => _('More recent products from network'), 228 + 'products_more_recent' => _('Highlights'),
245 }[asset.to_s + '_' + filter] 229 }[asset.to_s + '_' + filter]
246 end 230 end
247 231
@@ -284,21 +268,30 @@ class SearchController &lt; PublicController @@ -284,21 +268,30 @@ class SearchController &lt; PublicController
284 { :per_page => limit, :page => page } 268 { :per_page => limit, :page => page }
285 end 269 end
286 270
287 - def full_text_search(filters = []) 271 + def full_text_search(filters = [], options = {})
288 paginate_options = paginate_options(params[:page]) 272 paginate_options = paginate_options(params[:page])
289 asset_class = asset_class(@asset) 273 asset_class = asset_class(@asset)
290 274
291 - solr_options = {}  
292 - if !@results_only and asset_class.methods.include?('facets') 275 + solr_options = options
  276 + if !@results_only and asset_class.respond_to? :facets
293 solr_options.merge! asset_class.facets_find_options(params[:facet]) 277 solr_options.merge! asset_class.facets_find_options(params[:facet])
294 solr_options[:all_facets] = true 278 solr_options[:all_facets] = true
295 solr_options[:limit] = 0 if @facets_only 279 solr_options[:limit] = 0 if @facets_only
296 - #solr_options[:facets][:browse] << asset_class.facet_category_query.call(@category) if @category and asset_class.facet_category_query  
297 end 280 end
298 - solr_options[:order] = params[:order_by] if params[:order_by]  
299 solr_options[:filter_queries] ||= [] 281 solr_options[:filter_queries] ||= []
300 solr_options[:filter_queries] += filters 282 solr_options[:filter_queries] += filters
301 solr_options[:filter_queries] << "environment_id:#{environment.id}" 283 solr_options[:filter_queries] << "environment_id:#{environment.id}"
  284 + solr_options[:filter_queries] << asset_class.facet_category_query.call(@category) if @category
  285 +
  286 + solr_options[:boost_functions] ||= []
  287 + params[:order_by] = nil if params[:order_by] == 'none'
  288 + if params[:order_by]
  289 + order = SortOptions[@asset][params[:order_by].to_sym]
  290 + raise "Unknown order by" if order.nil?
  291 + order[:solr_opts].each do |opt, value|
  292 + solr_options[opt] = value.is_a?(Proc) ? instance_eval(&value) : value
  293 + end
  294 + end
302 295
303 ret = asset_class.find_by_contents(@query, paginate_options, solr_options) 296 ret = asset_class.find_by_contents(@query, paginate_options, solr_options)
304 @results[@asset] = ret[:results] 297 @results[@asset] = ret[:results]
app/helpers/search_helper.rb
1 module SearchHelper 1 module SearchHelper
2 2
  3 + MAP_SEARCH_LIMIT = 2000
  4 + LIST_SEARCH_LIMIT = 20
  5 + BLOCKS_SEARCH_LIMIT = 18
  6 + MULTIPLE_SEARCH_LIMIT = 8
  7 + DistFilt = 200
  8 + DistBoost = 50
  9 + SortOptions = {
  10 + :products => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  11 + :more_recent, {:label => _('More Recent'), :solr_opts => {:sort => 'updated_at desc, score desc'}},
  12 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  13 + :closest, {:label => _('Closest to me'), :if => proc{ logged_in? && (profile=current_user.person).lat && profile.lng },
  14 + :solr_opts => {:sort => "geodist() asc",
  15 + :latitude => proc{ current_user.person.lat }, :longitude => proc{ current_user.person.lng }}},
  16 + ],
  17 + :events => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  18 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  19 + ],
  20 + :articles => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  21 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  22 + :name, {:label => _('Most recent'), :solr_opts => {:sort => 'updated_at desc, score desc'}},
  23 + ],
  24 + :enterprises => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  25 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  26 + ],
  27 + :people => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  28 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  29 + ],
  30 + :communities => ActiveSupport::OrderedHash[ :none, {:label => _('Relevance')},
  31 + :name, {:label => _('Name'), :solr_opts => {:sort => 'name_sortable asc'}},
  32 + ],
  33 + }
  34 +
3 # FIXME remove it after search_controler refactored 35 # FIXME remove it after search_controler refactored
4 include EventsHelper 36 include EventsHelper
5 37
@@ -130,18 +162,14 @@ module SearchHelper @@ -130,18 +162,14 @@ module SearchHelper
130 end 162 end
131 163
132 def order_by(asset) 164 def order_by(asset)
133 - options = {  
134 - :products => [[_('Relevance'), ''], [_('More Recent'), 'updated_at desc'], [_('Name'), 'name_or_category_sort asc']],  
135 - :events => [[_('Relevance'), ''], [_('Name'), 'name_sort asc']],  
136 - :articles => [[_('Relevance'), ''], [_('Name'), 'name_sort asc'], [_('Most recent'), 'updated_at desc']],  
137 - :enterprises => [[_('Relevance'), ''], [_('Name'), 'name_sort asc']],  
138 - :people => [[_('Relevance'), ''], [_('Name'), 'name_sort asc']],  
139 - :communities => [[_('Relevance'), ''], [_('Name'), 'name_sort asc']],  
140 - } 165 + options = SortOptions[asset].map do |name, options|
  166 + next if options[:if] and ! instance_eval(&options[:if])
  167 + [_(options[:label]), name.to_s]
  168 + end.compact
141 169
142 content_tag('div', _('Sort results by ') + 170 content_tag('div', _('Sort results by ') +
143 - select_tag(asset.to_s + '[order]', options_for_select(options[asset], params[:order_by]),  
144 - {:onchange => "window.location=jQuery.param.querystring(window.location.href, { 'order_by' : this.options[this.selectedIndex].value})"}), 171 + select_tag(asset.to_s + '[order]', options_for_select(options, params[:order_by] || 'none'),
  172 + {:onchange => "window.location = jQuery.param.querystring(window.location.href, { 'order_by' : this.options[this.selectedIndex].value})"}),
145 :class => "search-ordering") 173 :class => "search-ordering")
146 end 174 end
147 175
app/models/article.rb
@@ -618,15 +618,14 @@ class Article &lt; ActiveRecord::Base @@ -618,15 +618,14 @@ class Article &lt; ActiveRecord::Base
618 def f_category 618 def f_category
619 self.categories.collect(&:name) 619 self.categories.collect(&:name)
620 end 620 end
621 - def name_sort 621 +
  622 + delegate :region, :region_id, :environment, :environment_id, :to => :profile
  623 + def name_sortable # give a different name for solr
622 name 624 name
623 end 625 end
624 def public 626 def public
625 self.public? 627 self.public?
626 end 628 end
627 - def environment_id  
628 - profile.environment_id  
629 - end  
630 public 629 public
631 630
632 acts_as_faceted :fields => { 631 acts_as_faceted :fields => {
@@ -639,16 +638,24 @@ class Article &lt; ActiveRecord::Base @@ -639,16 +638,24 @@ class Article &lt; ActiveRecord::Base
639 :category_query => proc { |c| "f_category:\"#{c.name}\"" }, 638 :category_query => proc { |c| "f_category:\"#{c.name}\"" },
640 :order => [:f_type, :f_published_at, :f_profile_type, :f_category] 639 :order => [:f_type, :f_published_at, :f_profile_type, :f_category]
641 640
642 - acts_as_searchable :additional_fields => [  
643 - {:name_sort => {:type => :string}},  
644 - {:public => {:type => :boolean}},  
645 - {:environment_id => {:type => :integer}},  
646 - ] + facets_fields_for_solr,  
647 - :exclude_fields => [:setting],  
648 - :include => [:profile, :comments, :categories],  
649 - :facets => facets_option_for_solr,  
650 - :boost => proc {|a| 10 if a.profile.enabled},  
651 - :if => proc{|a| ! ['RssFeed'].include?(a.class.name)} 641 + acts_as_searchable :fields => facets_fields_for_solr + [
  642 + # searched fields
  643 + {:name => {:type => :text, :boost => 2.0}},
  644 + {:slug => :text}, {:body => :text},
  645 + {:abstract => :text}, {:filename => :text},
  646 + # filtered fields
  647 + {:public => :boolean}, {:environment_id => :integer},
  648 + :language, :published,
  649 + # ordered/query-boosted fields
  650 + {:name_sortable => :string}, :last_changed_by_id, :published_at, :is_image,
  651 + :updated_at, :created_at,
  652 + ], :include => [
  653 + {:profile => {:fields => [:name, :identifier, :address, :nickname, :region_id, :lat, :lng]}},
  654 + {:comments => {:fields => [:title, :body, :author_name, :author_email]}},
  655 + {:categories => {:fields => [:name, :path, :slug, :lat, :lng, :acronym, :abbreviation]}},
  656 + ], :facets => facets_option_for_solr,
  657 + :boost => proc { |a| 10 if a.profile.enabled },
  658 + :if => proc{ |a| ! ['RssFeed'].include?(a.class.name) }
652 handle_asynchronously :solr_save 659 handle_asynchronously :solr_save
653 660
654 private 661 private
app/models/category.rb
@@ -15,9 +15,6 @@ class Category &lt; ActiveRecord::Base @@ -15,9 +15,6 @@ class Category &lt; ActiveRecord::Base
15 15
16 acts_as_filesystem 16 acts_as_filesystem
17 17
18 - acts_as_searchable :additional_fields => [{:name => {:boost => 2.0}}]  
19 - handle_asynchronously :solr_save  
20 -  
21 has_many :article_categorizations, :dependent => :destroy 18 has_many :article_categorizations, :dependent => :destroy
22 has_many :articles, :through => :article_categorizations 19 has_many :articles, :through => :article_categorizations
23 has_many :comments, :through => :articles 20 has_many :comments, :through => :articles
@@ -68,7 +65,7 @@ class Category &lt; ActiveRecord::Base @@ -68,7 +65,7 @@ class Category &lt; ActiveRecord::Base
68 end 65 end
69 66
70 def upcoming_events(limit = 10) 67 def upcoming_events(limit = 10)
71 - self.events.find(:all, :conditions => [ 'start_date >= ?', Date.today ], :order => 'start_date') 68 + self.events.paginate(:conditions => [ 'start_date >= ?', Date.today ], :order => 'start_date', :page => 1, :per_page => limit)
72 end 69 end
73 70
74 def display_in_menu? 71 def display_in_menu?
@@ -92,4 +89,23 @@ class Category &lt; ActiveRecord::Base @@ -92,4 +89,23 @@ class Category &lt; ActiveRecord::Base
92 self.children.find(:all, :conditions => {:display_in_menu => true}).empty? 89 self.children.find(:all, :conditions => {:display_in_menu => true}).empty?
93 end 90 end
94 91
  92 + private
  93 + def name_sortable # give a different name for solr
  94 + name
  95 + end
  96 + public
  97 +
  98 + acts_as_searchable :fields => [
  99 + # searched fields
  100 + {:name => {:type => :text, :boost => 2.0}},
  101 + {:path => :text}, {:slug => :text},
  102 + {:abbreviation => :text}, {:acronym => :text},
  103 + # filtered fields
  104 + :parent_id,
  105 + # ordered/query-boosted fields
  106 + {:name_sortable => :string},
  107 + ]
  108 + after_save_reindex [:articles, :profiles], :with => :delayed_job
  109 + handle_asynchronously :solr_save
  110 +
95 end 111 end
app/models/certifier.rb
@@ -24,4 +24,6 @@ class Certifier &lt; ActiveRecord::Base @@ -24,4 +24,6 @@ class Certifier &lt; ActiveRecord::Base
24 self.name.downcase.transliterate <=> b.name.downcase.transliterate 24 self.name.downcase.transliterate <=> b.name.downcase.transliterate
25 end 25 end
26 26
  27 + after_save_reindex [:products], :with => :delayed_job
  28 +
27 end 29 end
app/models/image.rb
@@ -23,4 +23,9 @@ class Image &lt; ActiveRecord::Base @@ -23,4 +23,9 @@ class Image &lt; ActiveRecord::Base
23 23
24 postgresql_attachment_fu 24 postgresql_attachment_fu
25 25
  26 + alias_method :public_filename_old, :public_filename
  27 + def public_filename(*args)
  28 + "http://cirandas.net#{public_filename_old(args)}"
  29 + end
  30 +
26 end 31 end
app/models/product_category.rb
1 class ProductCategory < Category 1 class ProductCategory < Category
  2 + # FIXME: do not allow category with products or inputs to be destroyed
2 has_many :products 3 has_many :products
3 has_many :inputs 4 has_many :inputs
4 5
@@ -9,4 +10,7 @@ class ProductCategory &lt; Category @@ -9,4 +10,7 @@ class ProductCategory &lt; Category
9 def self.menu_categories(top_category, env) 10 def self.menu_categories(top_category, env)
10 top_category ? top_category.children : top_level_for(env).select{|c|c.kind_of?(ProductCategory)} 11 top_category ? top_category.children : top_level_for(env).select{|c|c.kind_of?(ProductCategory)}
11 end 12 end
  13 +
  14 + after_save_reindex [:products], :with => :delayed_job
  15 +
12 end 16 end
app/models/profile.rb
@@ -87,7 +87,7 @@ class Profile &lt; ActiveRecord::Base @@ -87,7 +87,7 @@ class Profile &lt; ActiveRecord::Base
87 named_scope :more_popular 87 named_scope :more_popular
88 named_scope :more_active 88 named_scope :more_active
89 89
90 - named_scope :more_recent, :order => "updated_at DESC" 90 + named_scope :more_recent, :order => "created_at DESC"
91 91
92 acts_as_trackable :dependent => :destroy 92 acts_as_trackable :dependent => :destroy
93 93
@@ -125,8 +125,6 @@ class Profile &lt; ActiveRecord::Base @@ -125,8 +125,6 @@ class Profile &lt; ActiveRecord::Base
125 125
126 validates_length_of :description, :maximum => 550, :allow_nil => true 126 validates_length_of :description, :maximum => 550, :allow_nil => true
127 127
128 - acts_as_mappable :default_units => :kms  
129 -  
130 # Valid identifiers must match this format. 128 # Valid identifiers must match this format.
131 IDENTIFIER_FORMAT = /^#{Noosfero.identifier_format}$/ 129 IDENTIFIER_FORMAT = /^#{Noosfero.identifier_format}$/
132 130
@@ -859,7 +857,8 @@ private :generate_url, :url_options @@ -859,7 +857,8 @@ private :generate_url, :url_options
859 c.name 857 c.name
860 end 858 end
861 end 859 end
862 - def name_sort 860 +
  861 + def name_sortable # give a different name for solr
863 name 862 name
864 end 863 end
865 def public 864 def public
@@ -875,13 +874,24 @@ private :generate_url, :url_options @@ -875,13 +874,24 @@ private :generate_url, :url_options
875 :category_query => proc { |c| "f_categories:#{c.id}" }, 874 :category_query => proc { |c| "f_categories:#{c.id}" },
876 :order => [:f_region, :f_categories] 875 :order => [:f_region, :f_categories]
877 876
878 - acts_as_searchable :additional_fields => [  
879 - {:name_sort => {:type => :string}},  
880 - {:public => {:type => :boolean}},  
881 - :extra_data_for_index ] + facets.keys.map{|i| {i => :facet}},  
882 - :boost => proc {|p| 10 if p.enabled},  
883 - :include => [:categories, :region],  
884 - :facets => facets.keys 877 + acts_as_searchable :fields => facets_fields_for_solr + [:extra_data_for_index,
  878 + # searched fields
  879 + {:name => {:type => :text, :boost => 2.0}},
  880 + {:identifier => :text}, {:address => :text}, {:nickname => :text},
  881 + # filtered fields
  882 + {:public => :boolean}, {:environment_id => :integer},
  883 + # ordered/query-boosted fields
  884 + {:name_sortable => :string}, {:user_id => :integer},
  885 + :enabled, :active, :validated, :public_profile,
  886 + {:lat => :float}, {:lng => :float},
  887 + :updated_at, :created_at,
  888 + ],
  889 + :include => [
  890 + {:region => {:fields => [:name, :path, :slug, :lat, :lng]}},
  891 + {:categories => {:fields => [:name, :path, :slug, :lat, :lng, :acronym, :abbreviation]}},
  892 + ], :facets => facets_option_for_solr,
  893 + :boost => proc{ |p| 10 if p.enabled }
  894 + after_save_reindex [:articles], :with => :delayed_job
885 handle_asynchronously :solr_save 895 handle_asynchronously :solr_save
886 896
887 def control_panel_settings_button 897 def control_panel_settings_button
app/models/qualifier.rb
@@ -15,4 +15,6 @@ class Qualifier &lt; ActiveRecord::Base @@ -15,4 +15,6 @@ class Qualifier &lt; ActiveRecord::Base
15 self.name.downcase.transliterate <=> b.name.downcase.transliterate 15 self.name.downcase.transliterate <=> b.name.downcase.transliterate
16 end 16 end
17 17
  18 + after_save_reindex [:products], :with => :delayed_job
  19 +
18 end 20 end
app/views/search/_article.rhtml
@@ -3,9 +3,10 @@ @@ -3,9 +3,10 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => article %> 4 <%= render :partial => 'image', :object => article %>
5 </div> 5 </div>
6 - <div class="search-content-second-column">  
7 -  
8 - </div> 6 + <table class="noborder search-content-second-column">
  7 + <%= render :partial => 'article_common', :object => article %>
  8 + </table>
  9 + <%= render :partial => 'article_last_change', :object => article %>
9 10
10 <div style="clear:both"></div> 11 <div style="clear:both"></div>
11 </li> 12 </li>
app/views/search/_article_author.rhtml
1 <% article = article_author %> 1 <% article = article_author %>
2 2
3 -<div class="search-article-author">  
4 - <div class="search-article-author-name">  
5 - <span class="search-field-label"><%= _("Author") %></span> 3 +<tr class="search-article-author search-article-author-name">
  4 + <td class="search-field-label"><%= _("Author") %></td>
  5 + <td>
6 <%= link_to_profile article.profile.name, article.profile.identifier %> 6 <%= link_to_profile article.profile.name, article.profile.identifier %>
7 - </div>  
8 -</div> 7 + </td>
  8 +</tr>
app/views/search/_article_categories.rhtml
1 -<div class="search-article-categories">  
2 - <span class="search-field-label"><%= _('Categories') %></span>  
3 - <span class="search-article-categories-container"> 1 +<tr class="search-article-categories">
  2 + <td class="search-field-label"><%= _('Categories') %></td>
  3 + <td class="search-article-categories-container <%= "search-field-none" if article_categories.empty? %>">
4 <% article_categories.each do |category| %> 4 <% article_categories.each do |category| %>
5 <%= link_to_category category, false, :class => "search-article-category" %> 5 <%= link_to_category category, false, :class => "search-article-category" %>
6 <% end %> 6 <% end %>
7 - <span class="search-field-none"><%= _('None') if article_categories.empty? %></span>  
8 - </span>  
9 -</div> 7 + <%= _('None') if article_categories.empty? %>
  8 + </td>
  9 +</tr>
app/views/search/_article_common.rhtml
@@ -5,4 +5,3 @@ @@ -5,4 +5,3 @@
5 <%= render :partial => 'article_description', :object => article if show_description %> 5 <%= render :partial => 'article_description', :object => article if show_description %>
6 <%= render :partial => 'article_tags', :object => article.tags %> 6 <%= render :partial => 'article_tags', :object => article.tags %>
7 <%= render :partial => 'article_categories', :object => article.categories %> 7 <%= render :partial => 'article_categories', :object => article.categories %>
8 -<%= render :partial => 'article_last_change', :object => article %>  
app/views/search/_article_description.rhtml
1 <% article = article_description %> 1 <% article = article_description %>
2 2
3 -<div class="search-article-description">  
4 - <span class="search-field-label"><%= _("Description") %></span> 3 +<tr class="search-article-description">
  4 + <td class="search-field-label"><%= _("Description") %></td>
  5 +
5 <% if !article.body.blank? %> 6 <% if !article.body.blank? %>
6 - <% body_stripped = strip_tags(article.body.to_s) %>  
7 - <span class="search-article-body"><%= excerpt(body_stripped, body_stripped.first(3), 200) %></span> 7 + <% description = strip_tags(article.body.to_s) %>
  8 + <% description = excerpt(description, description.first(3), 200).gsub!(/\s{2,}/, ' ') %>
  9 + <td class="search-article-body"><%= description %></td>
8 <% else %> 10 <% else %>
9 - <span class="search-field-none"><%= _('None') %></span> 11 + <td class="search-field-none"><%= _('None') %></td>
10 <% end %> 12 <% end %>
11 -</div> 13 +</tr>
app/views/search/_article_last_change.rhtml
1 <% article = article_last_change %> 1 <% article = article_last_change %>
2 2
3 <div class="search-article-author-changes"> 3 <div class="search-article-author-changes">
4 - <% if article.last_changed_by && article.last_changed_by != article.profile %>  
5 - <span><%= _('by %s') % link_to(article.last_changed_by.name, article.last_changed_by.url) %>&nbsp<%= _(' at %s.') % show_date(article.updated_at) %></span> 4 + <% if article.last_changed_by and article.last_changed_by != article.profile %>
  5 + <span><%= _('by %{name} at %{date}') % {:name => link_to(article.last_changed_by.name, article.last_changed_by.url),
  6 + :date => show_date(article.updated_at) } %></span>
6 <% else %> 7 <% else %>
7 <span><%= _('Last update: %s.') % show_date(article.updated_at) %></span> 8 <span><%= _('Last update: %s.') % show_date(article.updated_at) %></span>
8 <% end %> 9 <% end %>
app/views/search/_article_tags.rhtml
1 -<div class="search-article-tags">  
2 - <span class="search-field-label"><%= _('Tags') %></span>  
3 - <span class="search-article-tags-container"> 1 +<tr class="search-article-tags">
  2 + <td class="search-field-label"><%= _('Tags') %></td>
  3 + <td class="search-article-tags-container <%= "search-field-none" if article_tags.empty? %>">
4 <% article_tags.each do |tag| %> 4 <% article_tags.each do |tag| %>
5 <%= link_to_tag tag, :class => "search-article-tag" %> 5 <%= link_to_tag tag, :class => "search-article-tag" %>
6 <% end %> 6 <% end %>
7 - <span class="search-field-none"><%= _('None') if article_tags.empty? %></span>  
8 - </span>  
9 -</div> 7 + <%= _('None') if article_tags.empty? %></td>
  8 + </td>
  9 +</tr>
app/views/search/_blog.rhtml
@@ -3,21 +3,22 @@ @@ -3,21 +3,22 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => blog %> 4 <%= render :partial => 'image', :object => blog %>
5 </div> 5 </div>
6 - <div class="search-content-second-column">  
7 - <div class="search-blog-items">  
8 - <span class="search-field-label"><%= _("Last posts") %></span> 6 + <table class="noborder search-content-second-column">
  7 + <tr class="search-blog-items">
  8 + <td class="search-field-label"><%= _("Last posts") %></td>
  9 +
9 <% r = blog.children.find(:all, :order => :updated_at, :conditions => ['type != ?', 'RssFeed']).last(3) %> 10 <% r = blog.children.find(:all, :order => :updated_at, :conditions => ['type != ?', 'RssFeed']).last(3) %>
10 - <% r.each do |a| %>  
11 - <%= link_to a.title, a.view_url, :class => 'search-blog-sample-item '+icon_for_article(a) %>  
12 - <% end %>  
13 - <span class="search-field-none"><%= _('None') if r.empty? %></span>  
14 - </div> 11 + <td class="<%= "search-field-none" if r.empty? %>">
  12 + <% r.each do |a| %>
  13 + <%= link_to a.title, a.view_url, :class => 'search-blog-sample-item '+icon_for_article(a) %>
  14 + <% end %>
  15 + <%= _('None') if r.empty? %>
  16 + </td>
  17 + </tr>
15 18
16 - <%= render :partial => 'article_author', :object => blog %>  
17 - <%= render :partial => 'article_tags', :object => blog.tags %>  
18 - <%= render :partial => 'article_categories', :object => blog.categories %>  
19 - <%= render :partial => 'article_last_change', :object => blog %>  
20 - </div> 19 + <%= render :partial => 'article_common', :object => blog %>
  20 + </table>
  21 + <%= render :partial => 'article_last_change', :object => blog %>
21 22
22 <div style="clear: both;"/></div> 23 <div style="clear: both;"/></div>
23 </li> 24 </li>
app/views/search/_display_results.rhtml
1 <div id="search-results" class="<%= 'only-one-result-box' if @results.size == 1 %>"> 1 <div id="search-results" class="<%= 'only-one-result-box' if @results.size == 1 %>">
2 -  
3 <% @order.each do |name| %> 2 <% @order.each do |name| %>
4 <% results = @results[name] %> 3 <% results = @results[name] %>
5 - <% if !results.nil? and !results.empty? %>  
6 - <div class="search-results-<%= name %> search-results-box">  
7 - 4 + <% empty = results.nil? || results.empty? %>
  5 +
  6 + <div class="search-results-<%= name %> search-results-box <%= "search-results-empty" if empty %>">
  7 + <% if not empty %>
  8 + <% partial = partial_for_class(results.first.class.class_name.constantize) %>
  9 +
8 <% if @results.size > 1 %> 10 <% if @results.size > 1 %>
9 <h3><%= @names[name] %></h3> 11 <h3><%= @names[name] %></h3>
10 - <%= link_to(results.respond_to?(:total_entries) ? _('see all (%d)') % results.total_entries : _('see all...'),  
11 - params.merge(:action => name), :class => 'see-more' ) %> 12 + <% if results.total_entries > SearchController::MULTIPLE_SEARCH_LIMIT %>
  13 + <%= link_to(_('see all (%d)') % results.total_entries, params.merge(:action => name), :class => 'see-more' ) %>
  14 + <% end %>
12 <% end %> 15 <% end %>
13 -  
14 - <% partial = partial_for_class(results.first.class.class_name.constantize) %> 16 +
15 <div class="search-results-innerbox search-results-type-<%= partial %> <%= 'common-profile-list-block' if partial == 'profile' %>"> 17 <div class="search-results-innerbox search-results-type-<%= partial %> <%= 'common-profile-list-block' if partial == 'profile' %>">
16 - <div class="search-results-innerbox2"><!-- the innerbox2 is a workarround for MSIE -->  
17 - <ul>  
18 - <% results.each do |hit| %>  
19 - <%= render :partial => partial_for_class(hit.class), :object => hit %>  
20 - <% end %>  
21 - </ul>  
22 - <hr />  
23 - </div><!-- end class="search-results-innerbox2" -->  
24 - </div><!-- end class="search-results-innerbox" -->  
25 -  
26 - </div><!-- end class="search-results-<%= name %>" -->  
27 - <% else %>  
28 - <div class="search-results-<%= name %> search-results-empty search-results-box"> 18 + <ul>
  19 + <% results.each do |hit| %>
  20 + <%= render :partial => partial_for_class(hit.class), :object => hit %>
  21 + <% end %>
  22 + </ul>
  23 + </div>
  24 + <% else %>
29 <% if @results.size > 1 %> 25 <% if @results.size > 1 %>
30 <h3><%= @names[name] %></h3> 26 <h3><%= @names[name] %></h3>
31 <% end %> 27 <% end %>
32 <div class="search-results-innerbox search-results-type-empty"> 28 <div class="search-results-innerbox search-results-type-empty">
33 <div> <%= _('None') %> </div> 29 <div> <%= _('None') %> </div>
34 - <hr />  
35 - </div><!-- end class="search-results-innerbox" -->  
36 - </div><!-- end class="search-results-<%= name %>" -->  
37 - <% end %> 30 + </div>
  31 + <% end %>
  32 + </div>
38 <% end %> 33 <% end %>
39 34
40 <div style="clear:both"></div> 35 <div style="clear:both"></div>
41 36
42 <%= add_zoom_to_images %> 37 <%= add_zoom_to_images %>
43 -</div><!-- end id="search-results" --> 38 +</div>
44 39
app/views/search/_event.rhtml
@@ -3,22 +3,23 @@ @@ -3,22 +3,23 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => event %> 4 <%= render :partial => 'image', :object => event %>
5 </div> 5 </div>
6 - <div class="search-content-second-column"> 6 + <table class="noborder search-content-second-column">
7 <% if event.start_date %> 7 <% if event.start_date %>
8 - <div class="searc-article-event-date">  
9 - <span class="search-field-label"><%= _('Start date') %></span>  
10 - <span class="article-item-date"><%= event.start_date %></span>  
11 - </div> 8 + <tr class="searc-article-event-date">
  9 + <td class="search-field-label"><%= _('Start date') %></td>
  10 + <td class="article-item-date"><%= event.start_date %></td>
  11 + </tr>
12 <% end %> 12 <% end %>
13 <% if event.end_date %> 13 <% if event.end_date %>
14 - <div class="searc-article-event-date">  
15 - <span class="search-field-label"><%= _('End date') %></span>  
16 - <span class="article-item-date"><%= event.end_date %></span>  
17 - </div> 14 + <tr class="searc-article-event-date">
  15 + <td class="search-field-label"><%= _('End date') %></td>
  16 + <td class="article-item-date"><%= event.end_date %></td>
  17 + </tr>
18 <% end %> 18 <% end %>
19 19
20 <%= render :partial => 'article_common', :object => event %> 20 <%= render :partial => 'article_common', :object => event %>
21 - </div> 21 + </table>
  22 + <%= render :partial => 'article_last_change', :object => event %>
22 23
23 <div style="clear: both"></div> 24 <div style="clear: both"></div>
24 </li> 25 </li>
app/views/search/_folder.rhtml
@@ -3,18 +3,22 @@ @@ -3,18 +3,22 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => folder %> 4 <%= render :partial => 'image', :object => folder %>
5 </div> 5 </div>
6 - <div class="search-content-second-column">  
7 - <div class="search-folder-items">  
8 - <span class="search-field-label"><%= _("Last items") %></span> 6 + <table class="noborder search-content-second-column">
  7 + <tr class="search-folder-items">
  8 + <td class="search-field-label"><%= _("Last items") %></td>
  9 +
9 <% r = folder.children.last(3) %> 10 <% r = folder.children.last(3) %>
10 - <% r.each do |a| %>  
11 - <%= link_to a.title, a.view_url, :class => 'search-folder-sample-item '+icon_for_article(a) %>  
12 - <% end %>  
13 - <span class="search-field-none"><%= _('None') if r.empty? %></span>  
14 - </div> 11 + <td class="<%= "search-field-none" if r.empty? %>">
  12 + <% r.each do |a| %>
  13 + <%= link_to a.title, a.view_url, :class => 'search-folder-sample-item '+icon_for_article(a) %>
  14 + <% end %>
  15 + <%= _('None') if r.empty? %>
  16 + </td>
  17 + </tr>
15 18
16 <%= render :partial => 'article_common', :object => folder %> 19 <%= render :partial => 'article_common', :object => folder %>
17 - </div> 20 + </table>
  21 + <%= render :partial => 'article_last_change', :object => folder %>
18 22
19 <div style="clear:both"></div> 23 <div style="clear:both"></div>
20 </li> 24 </li>
app/views/search/_forum.rhtml
@@ -3,18 +3,22 @@ @@ -3,18 +3,22 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => forum %> 4 <%= render :partial => 'image', :object => forum %>
5 </div> 5 </div>
6 - <div class="search-content-second-column">  
7 - <div class="search-forum-items">  
8 - <span class="search-field-label"><%= _("Last topics") %></span> 6 + <table class="noborder search-content-second-column">
  7 + <tr class="search-forum-items">
  8 + <td class="search-field-label"><%= _("Last topics") %></td>
  9 +
9 <% r = forum.children.find(:all, :order => :updated_at, :conditions => ['type != ?', 'RssFeed']).last(3) %> 10 <% r = forum.children.find(:all, :order => :updated_at, :conditions => ['type != ?', 'RssFeed']).last(3) %>
10 - <% r.each do |a| %>  
11 - <%= link_to a.title, a.view_url, :class => 'search-forum-sample-item '+icon_for_article(a) %>  
12 - <% end %>  
13 - <span class="search-field-none"><%= _('None') if r.empty? %></span>  
14 - </div> 11 + <td class="<%= "search-field-none" if r.empty? %>">
  12 + <% r.each do |a| %>
  13 + <%= link_to a.title, a.view_url, :class => 'search-forum-sample-item '+icon_for_article(a) %>
  14 + <% end %>
  15 + <%= _('None') if r.empty? %>
  16 + </td>
  17 + </tr>
15 18
16 <%= render :partial => 'article_common', :object => forum %> 19 <%= render :partial => 'article_common', :object => forum %>
17 - </div> 20 + </table>
  21 + <%= render :partial => 'article_last_change', :object => forum %>
18 22
19 <div style="clear:both"></div> 23 <div style="clear:both"></div>
20 </li> 24 </li>
app/views/search/_gallery.rhtml
@@ -3,9 +3,10 @@ @@ -3,9 +3,10 @@
3 <div class="search-content-first-column"> 3 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => gallery %> 4 <%= render :partial => 'image', :object => gallery %>
5 </div> 5 </div>
6 - <div class="search-content-second-column"> 6 + <table class="noborder search-content-second-column">
7 <%= render :partial => 'article_common', :object => gallery %> 7 <%= render :partial => 'article_common', :object => gallery %>
8 - </div> 8 + </table>
  9 + <%= render :partial => 'article_last_change', :object => gallery %>
9 10
10 <div style="clear: both"></div> 11 <div style="clear: both"></div>
11 </li> 12 </li>
app/views/search/_image.rhtml
1 <div class="search-image-container"> 1 <div class="search-image-container">
2 2
3 - <% if image.is_a? UploadedFile %> 3 + <% if image.is_a? UploadedFile and image.filename %>
4 <% extension = image.filename[(image.filename.rindex('.')+1)..-1].downcase %> 4 <% extension = image.filename[(image.filename.rindex('.')+1)..-1].downcase %>
5 <% if ['jpg', 'jpeg', 'gif', 'png', 'tiff', 'svg'].include? extension %> 5 <% if ['jpg', 'jpeg', 'gif', 'png', 'tiff', 'svg'].include? extension %>
6 <%= link_to '', image.view_url, :class => "search-image-pic", :style => 'background-image: url(%s)'% image.public_filename(:thumb) %> 6 <%= link_to '', image.view_url, :class => "search-image-pic", :style => 'background-image: url(%s)'% image.public_filename(:thumb) %>
app/views/search/_product.rhtml
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%> 2 <% extra_properties = @plugins.dispatch(:asset_product_properties, product)%>
3 3
4 <li class="search-product-item"> 4 <li class="search-product-item">
  5 +
5 <div class="search-product-item-first-column"> 6 <div class="search-product-item-first-column">
6 <%= render :partial => 'search/image', :object => product %> 7 <%= render :partial => 'search/image', :object => product %>
7 8
@@ -18,17 +19,9 @@ @@ -18,17 +19,9 @@
18 <% end %> 19 <% end %>
19 <% end %> 20 <% end %>
20 <div class="search-product-inputs-info"> 21 <div class="search-product-inputs-info">
21 - <% if product.inputs.count > product.inputs.collect(&:is_from_solidarity_economy).count(nil) %>  
22 - <% se_i = t_i = 0 %>  
23 - <% product.inputs.each{ |i| t_i += 1; se_i += 1 if i.is_from_solidarity_economy } %>  
24 - <% p = case (se_i.to_f/t_i)*100  
25 - when 0..24.999 then ["0", _("")];  
26 - when 25..49.999 then ["25", _("25%")];  
27 - when 50..74.999 then ["50", _("50%")];  
28 - when 75..99.999 then ["75", _("75%")];  
29 - when 100 then ["100", _("100%")];  
30 - end %>  
31 - <div class="search-product-percentage-from-solidarity-economy search-product-ecosol-percentage-icon-<%=p[0]%>" title="<%=_('Percentage of inputs from solidarity economy')%>"> 22 + <% if p = product.percentage_from_solidarity_economy %>
  23 + <div class="search-product-percentage-from-solidarity-economy search-product-ecosol-percentage-icon-<%= p[0].to_s %>"
  24 + title="<%=_('Percentage of inputs from solidarity economy')%>">
32 <%= p[1] %> 25 <%= p[1] %>
33 </div> 26 </div>
34 <% end %> 27 <% end %>
app/views/search/_profile.rhtml
@@ -39,6 +39,7 @@ @@ -39,6 +39,7 @@
39 <% end %> 39 <% end %>
40 </div> 40 </div>
41 </div> 41 </div>
  42 +
42 <hr class="clearfix" /> 43 <hr class="clearfix" />
43 </div> 44 </div>
44 <% end %> 45 <% end %>
app/views/search/_search_form.rhtml
@@ -5,10 +5,13 @@ @@ -5,10 +5,13 @@
5 5
6 <%= hidden_field_tag :display, params[:display] %> 6 <%= hidden_field_tag :display, params[:display] %>
7 7
8 - <% CGI::unescape(request.request_uri).split("&").each do |part| %>  
9 - <% if part.start_with? "facet" %>  
10 - <% name_value = part.split("=") %>  
11 - <%= hidden_field_tag name_value[0], name_value[1] %> 8 + <% params_uri = CGI::unescape(request.request_uri) %>
  9 + <% if params_uri.index('?') %>
  10 + <% params_uri[(params_uri.index('?')+1)..-1].to_s.split("&").each do |part| %>
  11 + <% if part.start_with? "facet" %>
  12 + <% name_value = part.split("=") %>
  13 + <%= hidden_field_tag name_value[0], name_value[1] %>
  14 + <% end %>
12 <% end %> 15 <% end %>
13 <% end %> 16 <% end %>
14 17
@@ -16,19 +19,9 @@ @@ -16,19 +19,9 @@
16 <span class="formfield"> 19 <span class="formfield">
17 <%= text_field_tag 'query', @query, :id => 'search-input', :size => 50 %> 20 <%= text_field_tag 'query', @query, :id => 'search-input', :size => 50 %>
18 <%= javascript_tag "jQuery('#search-input').attr('title', \"#{hint}\").hint()" if defined?(hint) %> 21 <%= javascript_tag "jQuery('#search-input').attr('title', \"#{hint}\").hint()" if defined?(hint) %>
19 - <%= javascript_tag "jQuery('.search_form').submit(function() {  
20 - if (jQuery('#search-input').val().length < 3) {  
21 - jQuery('#search-empty-query-error').slideDown(200).delay(2500).slideUp(200);  
22 - return false;  
23 - }  
24 - });" %>  
25 </span> 22 </span>
26 23
27 <%= submit_button(:search, _('Search')) %> 24 <%= submit_button(:search, _('Search')) %>
28 -  
29 - <div id="search-empty-query-error">  
30 - <%= _("Type more than 2 characters to start a search") %>  
31 - </div>  
32 </div> 25 </div>
33 26
34 <% end %> 27 <% end %>
app/views/search/_text_article.rhtml
1 <li class="search-text-article-item article-item"> 1 <li class="search-text-article-item article-item">
2 <%= link_to(text_article.title, text_article.url, :class => "search-result-title") %> 2 <%= link_to(text_article.title, text_article.url, :class => "search-result-title") %>
  3 +
3 <div class="search-content-first-column"> 4 <div class="search-content-first-column">
4 <%= render :partial => 'image', :object => text_article %> 5 <%= render :partial => 'image', :object => text_article %>
5 </div> 6 </div>
6 - <div class="search-content-second-column"> 7 + <table class="noborder search-content-second-column">
7 <%= render :partial => 'article_common', :object => text_article %> 8 <%= render :partial => 'article_common', :object => text_article %>
8 - </div> 9 + </table>
  10 + <%= render :partial => 'article_last_change', :object => text_article %>
9 11
10 <div style="clear: both"></div> 12 <div style="clear: both"></div>
11 </li> 13 </li>
app/views/search/_uploaded_file.rhtml
1 <li class="search-uploaded-file-item article-item"> 1 <li class="search-uploaded-file-item article-item">
2 <%= link_to uploaded_file.filename, uploaded_file.view_url, :class => 'search-result-title' %> 2 <%= link_to uploaded_file.filename, uploaded_file.view_url, :class => 'search-result-title' %>
3 - <hr class="clear" />  
4 3
5 <div class="search-content-first-column"> 4 <div class="search-content-first-column">
6 <%= render :partial => 'image', :object => uploaded_file %> 5 <%= render :partial => 'image', :object => uploaded_file %>
7 </div> 6 </div>
8 7
9 - <div class="search-uploaded-file-second-column"> 8 + <table class="noborder search-content-second-column">
10 <%= render :partial => 'article_author', :object => uploaded_file %> 9 <%= render :partial => 'article_author', :object => uploaded_file %>
  10 + <%= render :partial => 'article_description', :object => uploaded_file %>
11 11
12 - <div class="search-uploaded-file-description">  
13 - <% if !uploaded_file.body.blank? %>  
14 - <span class="search-field-label"><%= _("Description") %></span>  
15 - <% body = strip_tags(uploaded_file.body.to_s) %>  
16 - <%= excerpt(body, body.first(3), 200) %>  
17 - <% end %>  
18 - </div>  
19 -  
20 - <div class="search-uploaded-file-parent">  
21 - <% if uploaded_file.parent && uploaded_file.parent.published? %>  
22 - <% if uploaded_file.parent.gallery? %>  
23 - <span class="search-field-label"><%= _("Gallery") %></span>  
24 - <% else %>  
25 - <span class="search-field-label"><%= _("Folder") %></span>  
26 - <% end %>  
27 - <%= link_to uploaded_file.parent.name, {:controller => 'content_viewer', :profile => uploaded_file.profile.identifier, :action => 'view_page', :page => [uploaded_file.parent.slug]} %>  
28 - <% end %>  
29 - </div> 12 + <% if uploaded_file.parent and uploaded_file.parent.published? %>
  13 + <tr class="search-uploaded-file-parent">
  14 + <td class="search-field-label"><%= uploaded_file.parent.gallery? ? _("Gallery") : _("Folder") %></td>
  15 + <td><%= link_to uploaded_file.parent.name, uploaded_file.parent.url %></td>
  16 + </tr>
  17 + <% end %>
30 18
31 <%= render :partial => 'article_tags', :object => uploaded_file.tags %> 19 <%= render :partial => 'article_tags', :object => uploaded_file.tags %>
32 <%= render :partial => 'article_categories', :object => uploaded_file.categories %> 20 <%= render :partial => 'article_categories', :object => uploaded_file.categories %>
33 - <%= render :partial => 'article_last_change', :object => uploaded_file %>  
34 - </div> 21 + </table>
  22 + <%= render :partial => 'article_last_change', :object => uploaded_file %>
35 23
36 <div style="clear:both"></div> 24 <div style="clear:both"></div>
37 </li> 25 </li>
app/views/search/products.rhtml
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 <%= render :partial => 'results_header' %> 14 <%= render :partial => 'results_header' %>
15 15
16 <%= display_results(true) %> 16 <%= display_results(true) %>
17 - <% if params[:display] != 'map' %> 17 + <% if !@one_page and params[:display] != 'map' %>
18 <%= pagination_links @results[:products] %> 18 <%= pagination_links @results[:products] %>
19 <% end %> 19 <% end %>
20 </div> 20 </div>
app/views/search/tag.rhtml
@@ -7,16 +7,7 @@ @@ -7,16 +7,7 @@
7 <% end %> 7 <% end %>
8 8
9 <% cache_timeout(@tag_cache_key, 4.hour) do %> 9 <% cache_timeout(@tag_cache_key, 4.hour) do %>
10 - <div class='search-tagged-items'>  
11 - <% @tagged.each do |hit| %>  
12 - <ul class="clean-list">  
13 - <%= render :partial => partial_for_class(hit.class), :object => hit %>  
14 - </ul>  
15 - <br style='clear: left;'/>  
16 - <% end %>  
17 -  
18 - </div>  
19 - <%= pagination_links @tagged, :param_name => 'npage' %> 10 + <%= display_results %>
20 11
21 <div style="clear: both"></div> 12 <div style="clear: both"></div>
22 <% end %> 13 <% end %>
public/designs/themes/base/style.css
@@ -872,30 +872,6 @@ X.sep { @@ -872,30 +872,6 @@ X.sep {
872 border-top: 1px solid #CCC; 872 border-top: 1px solid #CCC;
873 } 873 }
874 874
875 -#content .search-results-box a.see-more {  
876 - z-index: 10;  
877 - position: absolute;  
878 - bottom: 20px;  
879 - right: 20px;  
880 - background: transparent url(imgs/arrow-right-p.png) 100% 55% no-repeat;  
881 - border: none;  
882 - padding-right: 15px;  
883 - color: #000;  
884 - text-decoration: none;  
885 - font-weight: bold;  
886 -}  
887 -#content .search-results-box a.see-more:hover {  
888 - background: transparent url(imgs/arrow-right-p.png) 100% 55% no-repeat;  
889 - color: #888;  
890 -}  
891 -  
892 -  
893 -.search-results-type-article.search-results-innerbox {  
894 - padding: 5px 0px 5px 10px;  
895 - max-height: 215px;  
896 - height: 225px;  
897 -}  
898 -  
899 #content .search-results-type-article li, 875 #content .search-results-type-article li,
900 #content .search-results-type-event li { 876 #content .search-results-type-event li {
901 padding: 5px 0px; 877 padding: 5px 0px;
@@ -1207,12 +1183,6 @@ table.profile th { @@ -1207,12 +1183,6 @@ table.profile th {
1207 1183
1208 /**************************** Browse *******************************/ 1184 /**************************** Browse *******************************/
1209 1185
1210 -.search-results-type-article.search-results-innerbox {  
1211 - padding: 5px 0px 5px 10px;  
1212 - max-height: 215px;  
1213 - height: 225px;  
1214 -}  
1215 -  
1216 #content .search-results-type-article li { 1186 #content .search-results-type-article li {
1217 padding: 5px 0px; 1187 padding: 5px 0px;
1218 } 1188 }
public/stylesheets/application.css
@@ -3216,7 +3216,7 @@ div#article-parent { @@ -3216,7 +3216,7 @@ div#article-parent {
3216 #agenda .calendar-day-out.selected { 3216 #agenda .calendar-day-out.selected {
3217 background: none; 3217 background: none;
3218 } 3218 }
3219 -#agenda td { 3219 +#agenda .agenda-calendar td {
3220 vertical-align: middle; 3220 vertical-align: middle;
3221 } 3221 }
3222 #agenda .agenda-calendar .current-month caption { 3222 #agenda .agenda-calendar .current-month caption {
public/stylesheets/search.css
@@ -91,7 +91,8 @@ @@ -91,7 +91,8 @@
91 margin: 0px; 91 margin: 0px;
92 padding: 0px; 92 padding: 0px;
93 } 93 }
94 -.controller-search #product-categories-menu .sub-opening, .controller-search #product-categories-menu .sub-closeing { 94 +.controller-search #product-categories-menu .sub-opening,
  95 +.controller-search #product-categories-menu .sub-closeing {
95 background-color: #FF8; 96 background-color: #FF8;
96 } 97 }
97 .controller-search #product-categories-menu .sub-opened { 98 .controller-search #product-categories-menu .sub-opened {
@@ -101,9 +102,13 @@ @@ -101,9 +102,13 @@
101 float: right; 102 float: right;
102 } 103 }
103 .controller-search #content .search-results-box a.see-more { 104 .controller-search #content .search-results-box a.see-more {
  105 + z-index: 10;
104 position: absolute; 106 position: absolute;
105 bottom: 0px; 107 bottom: 0px;
106 right: 25px; 108 right: 25px;
  109 + color: black;
  110 + text-decoration: none;
  111 + font-weight: bold;
107 font-size: 11px; 112 font-size: 11px;
108 line-height: 11px; 113 line-height: 11px;
109 background: #B8CFE7; 114 background: #B8CFE7;
@@ -116,10 +121,14 @@ @@ -116,10 +121,14 @@
116 color: #FFF; 121 color: #FFF;
117 text-decoration: none; 122 text-decoration: none;
118 } 123 }
  124 +.controller-search .search-results-innerbox.common-profile-list-block {
  125 + overflow: hidden;
  126 +}
119 .controller-search .search-results-innerbox { 127 .controller-search .search-results-innerbox {
120 - padding: 8px 0px 10px 10px; 128 + padding: 8px 10px 10px 10px;
  129 + overflow: auto;
  130 + overflow-x: hidden;
121 height: 230px; 131 height: 230px;
122 - overflow: visible;  
123 position: relative; /* work-arround-bug fo MSIE */ 132 position: relative; /* work-arround-bug fo MSIE */
124 } 133 }
125 .controller-search .search-results-innerbox hr { 134 .controller-search .search-results-innerbox hr {
@@ -138,7 +147,10 @@ @@ -138,7 +147,10 @@
138 height: 0px; 147 height: 0px;
139 visibility: hidden; 148 visibility: hidden;
140 } 149 }
141 -.controller-search #content .search-results-type-article ul, .controller-search #content .search-results-type-article li, .controller-search #content .search-results-type-event ul, .controller-search #content .search-results-type-event li { 150 +.search-results-type-article ul,
  151 +.controller-search #content .search-results-type-article li,
  152 +.controller-search #content .search-results-type-event ul,
  153 +.controller-search #content .search-results-type-event li {
142 margin: 0px; 154 margin: 0px;
143 padding: 0px; 155 padding: 0px;
144 list-style: none; 156 list-style: none;
@@ -146,21 +158,19 @@ @@ -146,21 +158,19 @@
146 .controller-search #content .search-results-type-event li { 158 .controller-search #content .search-results-type-event li {
147 padding: 2px 0px 4px 0px; 159 padding: 2px 0px 4px 0px;
148 } 160 }
149 -.controller-search #content .search-results-type-article li, .controller-search #content .search-results-type-event li {  
150 - padding: 0px 0px 4px 20px; 161 +.controller-search #content .search-results-type-article li {
  162 + padding: 10px 0;
151 background-repeat: no-repeat; 163 background-repeat: no-repeat;
152 - border-color: transparent;  
153 } 164 }
154 -.controller-search #content .search-results-type-article li:hover, .controller-search #content .search-results-type-event li:hover { 165 +.controller-search #content .search-results-type-article li:hover,
  166 +.controller-search #content .search-results-type-event li:hover {
155 background-color: transparent; 167 background-color: transparent;
156 } 168 }
157 -.controller-search .search-results-type-article .item_meta, .controller-search .search-results-type-event .item_meta { 169 +.controller-search .search-results-type-article .item_meta,
  170 +.controller-search .search-results-type-event .item_meta {
158 font-size: 10px; 171 font-size: 10px;
159 color: #888; 172 color: #888;
160 } 173 }
161 -.search-results-type-article.search-results-innerbox {  
162 - overflow: auto;  
163 -}  
164 #content .only-one-result-box .search-results-enterprises li.vcard { 174 #content .only-one-result-box .search-results-enterprises li.vcard {
165 margin: 4.5px; 175 margin: 4.5px;
166 } 176 }
@@ -171,19 +181,14 @@ @@ -171,19 +181,14 @@
171 min-height: 16px; 181 min-height: 16px;
172 overflow: hidden; 182 overflow: hidden;
173 } 183 }
174 -.controller-search #content .search-results-type-article ul { 184 +.search-results-type-article ul {
175 margin: 0; 185 margin: 0;
176 - padding:0;  
177 -}  
178 -.controller-search #content .search-results-type-article li.article-item,  
179 -.controller-search .search-tagged-items li.article-item,  
180 -.controller-search #content .only-one-result-box li.search-product-item {  
181 - padding:0;  
182 - border-bottom:1px solid #000;  
183 - margin: 0 0 10px 0;  
184 - padding: 0 0 10px 0;  
185 - font-size: 11px;  
186 - height: auto; 186 + padding: 0;
  187 +}
  188 +.search-results-type-article li.article-item,
  189 +.search-tagged-items li.article-item,
  190 +.only-one-result-box li.search-product-item {
  191 + border-bottom: 1px solid #000;
187 } 192 }
188 .search-result-title { 193 .search-result-title {
189 display:inline-block; 194 display:inline-block;
@@ -199,8 +204,6 @@ @@ -199,8 +204,6 @@
199 } 204 }
200 li.article-item .search-result-title { 205 li.article-item .search-result-title {
201 color: #007788; 206 color: #007788;
202 - clear:both;  
203 - float:left;  
204 } 207 }
205 .search-article-tags, 208 .search-article-tags,
206 .search-uploaded-file-description, 209 .search-uploaded-file-description,
@@ -216,37 +219,24 @@ li.article-item .search-result-title { @@ -216,37 +219,24 @@ li.article-item .search-result-title {
216 clear: both; 219 clear: both;
217 } 220 }
218 .search-article-body { 221 .search-article-body {
219 - float: right;  
220 width: 80%; 222 width: 80%;
221 margin-bottom: 7px; 223 margin-bottom: 7px;
222 } 224 }
223 .search-field-none { 225 .search-field-none {
224 color: #ccc; 226 color: #ccc;
225 } 227 }
226 -.search-forum-items .search-field-label {  
227 - height: 36px;  
228 -}  
229 .search-field-label { 228 .search-field-label {
230 - display: block;  
231 - float: left;  
232 font-size: 10px; 229 font-size: 10px;
233 font-weight: bold; 230 font-weight: bold;
234 - height: 18px;  
235 - line-height: 18px;  
236 - margin: 0 5px 0 0;  
237 text-transform: uppercase; 231 text-transform: uppercase;
238 width: 110px; 232 width: 110px;
  233 + display: block;
239 } 234 }
240 .search-product-item-third-column .search-field-label { 235 .search-product-item-third-column .search-field-label {
241 width: 150px; 236 width: 150px;
242 } 237 }
243 -li.article-item .search-article-tags-container,  
244 -li.article-item .search-article-categories-container {  
245 - display: block;  
246 - min-height: 18px;  
247 - line-height: 18px;  
248 -}  
249 -.search-article-tags .search-article-tag, .search-article-categories .search-article-category { 238 +.search-article-tags .search-article-tag,
  239 +.search-article-categories .search-article-category {
250 background: #BBB; 240 background: #BBB;
251 padding:0 2px; 241 padding:0 2px;
252 -moz-border-radius: 3px; 242 -moz-border-radius: 3px;
@@ -260,10 +250,12 @@ li.article-item .search-article-categories-container { @@ -260,10 +250,12 @@ li.article-item .search-article-categories-container {
260 color: #FFF; 250 color: #FFF;
261 font-size: 10px; 251 font-size: 10px;
262 } 252 }
263 -.search-article-tags a.search-article-tag, .search-article-categories a.search-article-category { 253 +.search-article-tags a.search-article-tag,
  254 +.search-article-categories a.search-article-category {
264 text-decoration:none; 255 text-decoration:none;
265 } 256 }
266 -.search-article-tags a:hover.search-article-tag, .search-article-categories a:hover.search-article-category { 257 +.search-article-tags a:hover.search-article-tag,
  258 +.search-article-categories a:hover.search-article-category {
267 background: #555753; 259 background: #555753;
268 text-decoration:none; 260 text-decoration:none;
269 } 261 }
@@ -292,7 +284,8 @@ li.article-item .search-article-categories-container { @@ -292,7 +284,8 @@ li.article-item .search-article-categories-container {
292 .search-results-innerbox .menu-submenu { 284 .search-results-innerbox .menu-submenu {
293 bottom: 107px; 285 bottom: 107px;
294 } 286 }
295 -.controller-search .search-results-type-product, .controller-search .search-results-type-event { 287 +.controller-search .search-results-type-product,
  288 +.controller-search .search-results-type-event {
296 overflow: auto; 289 overflow: auto;
297 } 290 }
298 li.search-product-item { 291 li.search-product-item {
@@ -364,8 +357,8 @@ li.search-product-item { @@ -364,8 +357,8 @@ li.search-product-item {
364 float: left; 357 float: left;
365 font-size: 70%; 358 font-size: 70%;
366 background-repeat: no-repeat; 359 background-repeat: no-repeat;
367 - height: 18px;  
368 - line-height: 18px; 360 + height: 21px;
  361 + line-height: 21px;
369 } 362 }
370 .search-product-ecosol-percentage-icon-0 { 363 .search-product-ecosol-percentage-icon-0 {
371 background-image: none; 364 background-image: none;
@@ -376,7 +369,8 @@ li.search-product-item { @@ -376,7 +369,8 @@ li.search-product-item {
376 .search-product-ecosol-percentage-icon-50 { 369 .search-product-ecosol-percentage-icon-50 {
377 background-position: 0px -42px; 370 background-position: 0px -42px;
378 } 371 }
379 -.search-product-ecosol-percentage-icon-75 { 372 +.search-product-ecosol-percentage-icon-75,
  373 +.search-product-ecosol-percentage-icon-100 {
380 background-position: 0px -63px; 374 background-position: 0px -63px;
381 } 375 }
382 .search-product-price-details { 376 .search-product-price-details {
@@ -388,18 +382,23 @@ li.search-product-item { @@ -388,18 +382,23 @@ li.search-product-item {
388 .controller-search #category-comments { 382 .controller-search #category-comments {
389 margin-left: 55%; 383 margin-left: 55%;
390 } 384 }
391 -.controller-search #content .search-results-comments ul, .controller-search #content .search-results-comments li, .controller-search #content #category-comments ul, .controller-search #content #category-comments li { 385 +.controller-search #content .search-results-comments ul,
  386 +.controller-search #content .search-results-comments li,
  387 +.controller-search #content #category-comments ul,
  388 +.controller-search #content #category-comments li {
392 margin: 0px; 389 margin: 0px;
393 padding: 0px; 390 padding: 0px;
394 list-style: none; 391 list-style: none;
395 color: #888; 392 color: #888;
396 } 393 }
397 -.controller-search .search-results-comments .comment-picture, .controller-search #category-comments .comment-picture { 394 +.controller-search .search-results-comments .comment-picture,
  395 +.controller-search #category-comments .comment-picture {
398 width: 20px; 396 width: 20px;
399 height: 20px; 397 height: 20px;
400 margin: -2px 5px 0px 0px; 398 margin: -2px 5px 0px 0px;
401 } 399 }
402 -.controller-search #content .search-results-type-product ul, .controller-search #content .search-results-type-product li { 400 +.controller-search #content .search-results-type-product ul,
  401 +.controller-search #content .search-results-type-product li {
403 margin: 0px; 402 margin: 0px;
404 padding: 0px; 403 padding: 0px;
405 list-style: none; 404 list-style: none;
@@ -572,7 +571,9 @@ li.search-product-item hr { @@ -572,7 +571,9 @@ li.search-product-item hr {
572 border: none; 571 border: none;
573 background: none; 572 background: none;
574 } 573 }
575 -.search-product-item-first-column, .search-product-item-second-column, .search-product-item-third-column { 574 +.search-product-item-first-column,
  575 +.search-product-item-second-column,
  576 +.search-product-item-third-column {
576 float: left; 577 float: left;
577 margin:0 !important; 578 margin:0 !important;
578 } 579 }
@@ -600,7 +601,6 @@ li.search-product-item hr { @@ -600,7 +601,6 @@ li.search-product-item hr {
600 } 601 }
601 .only-one-result-box .common-profile-list-block { 602 .only-one-result-box .common-profile-list-block {
602 float: left; 603 float: left;
603 - margin: 10px;  
604 } 604 }
605 .search-enterprise-item { 605 .search-enterprise-item {
606 border-bottom: 1px solid #ccc; 606 border-bottom: 1px solid #ccc;
@@ -760,30 +760,41 @@ form .search-field input:hover.button.submit { @@ -760,30 +760,41 @@ form .search-field input:hover.button.submit {
760 background-repeat: no-repeat; 760 background-repeat: no-repeat;
761 padding: 0 0 0 20px; 761 padding: 0 0 0 20px;
762 } 762 }
763 -.facet-menu a.facet-options-toggle.facet-more-options, .facet-menu a.facet-options-toggle { 763 +.facet-menu a.facet-options-toggle.facet-more-options,
  764 +.facet-menu a.facet-options-toggle {
764 background-position: 0 0; 765 background-position: 0 0;
765 } 766 }
766 .facet-menu a.facet-options-toggle.facet-less-options { 767 .facet-menu a.facet-options-toggle.facet-less-options {
767 background-position: 0 -32px; 768 background-position: 0 -32px;
768 } 769 }
769 770
770 -.search-uploaded-file-first-column,  
771 -.search-text-article-first-column,  
772 .search-content-first-column { 771 .search-content-first-column {
773 - clear: both;  
774 - float: left;  
775 - margin: 0 10px 0 0;  
776 width: 130px; 772 width: 130px;
777 - min-height:98px; 773 + min-height: 98px;
  774 + position: absolute;
778 } 775 }
779 -.search-uploaded-file-second-column,  
780 -.search-text-article-second-column,  
781 .search-content-second-column { 776 .search-content-second-column {
782 - float: left;  
783 - width: 570px; 777 + margin-left: 140px;
  778 + width: auto;
  779 +}
  780 +.search-content-second-column tr:hover {
  781 + background-color: none;
  782 +}
  783 +.search-content-second-column td {
  784 + height: auto;
  785 +}
  786 +.search-results-articles li {
  787 + position: relative;
  788 +}
  789 +
  790 +a.search-blog-sample-item.icon,
  791 +.search-folder-items a,
  792 +.search-forum-items a,
  793 +.search-blog-items a {
  794 + border: none;
  795 + margin-bottom: 3px;
  796 + display: block;
784 } 797 }
785 -ul.clean-list .search-uploaded-file-second-column,  
786 -ul.clean-list .search-text-article-second-column,  
787 ul.clean-list .search-content-second-column { 798 ul.clean-list .search-content-second-column {
788 width:795px; 799 width:795px;
789 } 800 }
@@ -864,10 +875,7 @@ a.search-image-pic { @@ -864,10 +875,7 @@ a.search-image-pic {
864 } 875 }
865 .search-content-first-column .icon-application-vnd-oasis-opendocument-text, 876 .search-content-first-column .icon-application-vnd-oasis-opendocument-text,
866 .search-content-first-column .icon-application-vnd-oasis-opendocument-spreadsheet, 877 .search-content-first-column .icon-application-vnd-oasis-opendocument-spreadsheet,
867 -.search-content-first-column .icon-text-content,  
868 -.search-uploaded-file-first-column .icon-application-vnd-oasis-opendocument-text,  
869 -.search-uploaded-file-first-column .icon-application-vnd-oasis-opendocument-spreadsheet,  
870 -.search-text-article-first-column .icon-text-content { 878 +.search-content-first-column .icon-text-content {
871 display:block; 879 display:block;
872 width: 16px; 880 width: 16px;
873 height: 16px; 881 height: 16px;
@@ -879,18 +887,6 @@ a.search-image-pic { @@ -879,18 +887,6 @@ a.search-image-pic {
879 margin: auto; 887 margin: auto;
880 888
881 } 889 }
882 -.search-content-first-column .icon-application-vnd-oasis-opendocument-spreadsheet,  
883 -.search-content-first-column .icon-application-vnd-oasis-opendocument-spreadsheet {  
884 -  
885 -}  
886 -.search-content-first-column .icon-application-opendocument-spreadsheet,  
887 -.search-uploaded-file-first-column .icon-application-opendocument-spreadsheet {  
888 -  
889 -}  
890 -.search-content-first-column .icon-text-content,  
891 -.search-text-article-first-column .icon-text-content {  
892 -  
893 -}  
894 .search-uploaded-file-line { 890 .search-uploaded-file-line {
895 clear: both; 891 clear: both;
896 float: left; 892 float: left;
@@ -904,7 +900,6 @@ a.search-image-pic { @@ -904,7 +900,6 @@ a.search-image-pic {
904 width: 130px; 900 width: 130px;
905 height: 130px; 901 height: 130px;
906 display: block; 902 display: block;
907 -  
908 } 903 }
909 #search-input { 904 #search-input {
910 font-size: 140%; 905 font-size: 140%;
@@ -933,7 +928,8 @@ a.search-image-pic { @@ -933,7 +928,8 @@ a.search-image-pic {
933 font-style: italic; 928 font-style: italic;
934 color: gray; 929 color: gray;
935 } 930 }
936 -.search-relevance, .search-result-text { 931 +.search-relevance,
  932 +.search-result-text {
937 margin-left: 40px; 933 margin-left: 40px;
938 } 934 }
939 935