diff --git a/Gemfile b/Gemfile index 10682fa7..c59f2cd1 100644 --- a/Gemfile +++ b/Gemfile @@ -2,12 +2,12 @@ source :rubygems # add in all the runtime dependencies -gem 'rails', '>= 3.0.5' +gem 'rails', '>= 3.0.7' gem 'warden' gem 'devise', '= 1.1.3' -gem 'mongoid', '2.0.0.rc.7' +gem 'mongoid', '~> 2.0.1' gem 'bson_ext', '~> 1.3.0' gem 'locomotive_mongoid_acts_as_tree', '0.1.5.5', :require => 'mongoid_acts_as_tree' gem 'will_paginate' @@ -20,7 +20,7 @@ gem 'inherited_resources', '~> 1.1.2' gem 'rmagick', '2.12.2' gem 'locomotive_carrierwave', '0.5.0.1.beta3', :require => 'carrierwave' -gem 'custom_fields', '1.0.0.beta.10' +gem 'custom_fields', '1.0.0.beta.12' gem 'fog', '0.3.7' gem 'mimetype-fu' gem 'actionmailer-with-request', :require => 'actionmailer_with_request' diff --git a/Gemfile.lock b/Gemfile.lock index 048c2ae2..9f6b95ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/floehopper/mocha.git - revision: 6fe08197f214457ac122c49d96fdac8e17adbea2 + revision: 7dd9512a874f89b8037d7320fe7988c222f439e1 specs: mocha (0.9.12.20110213002255) @@ -51,12 +51,12 @@ GEM activesupport (= 3.0.7) activesupport (3.0.7) archive-tar-minitar (0.5.2) - arel (2.0.9) + arel (2.0.10) autotest (4.4.6) ZenTest (>= 4.4.1) bcrypt-ruby (2.1.4) - bson (1.3.0) - bson_ext (1.3.0) + bson (1.3.1) + bson_ext (1.3.1) builder (2.1.2) bushido (0.0.12) highline (>= 1.6.1) @@ -72,7 +72,7 @@ GEM selenium-webdriver (>= 0.0.27) xpath (~> 0.1.2) celerity (0.8.9) - childprocess (0.1.8) + childprocess (0.1.9) ffi (~> 1.0.6) closure-compiler (1.1.1) columnize (0.3.2) @@ -87,10 +87,10 @@ GEM cucumber-rails (0.3.2) cucumber (>= 0.8.0) culerity (0.2.15) - custom_fields (1.0.0.beta.10) - activesupport (>= 3.0.4) + custom_fields (1.0.0.beta.12) + activesupport (>= 3.0.7) locomotive_carrierwave - mongoid (~> 2.0.0.rc.7) + mongoid (~> 2.0.1) daemons (1.1.3) database_cleaner (0.6.7) delayed_job (2.1.4) @@ -110,8 +110,7 @@ GEM factory_girl_rails (1.0.1) factory_girl (~> 1.3) railties (>= 3.0.0) - ffi (1.0.7) - rake (>= 0.8.7) + ffi (1.0.8) fog (0.3.7) builder excon (>= 0.2.3) @@ -134,8 +133,8 @@ GEM heroku (1.19.1) activesupport (>= 2.1.0) launchy (~> 0.3.2) - rest-client (>= 1.4.0, < 1.7.0) - highline (1.6.1) + rest-client (< 1.7.0, >= 1.4.0) + highline (1.6.2) httparty (0.7.7) crack (= 0.1.8) i18n (0.5.0) @@ -171,11 +170,11 @@ GEM mime-types (1.16) mimemagic (0.1.8) mimetype-fu (0.1.2) - mongo (1.3.0) - bson (>= 1.3.0) - mongoid (2.0.0.rc.7) + mongo (1.3.1) + bson (>= 1.3.1) + mongoid (2.0.1) activemodel (~> 3.0) - mongo (~> 1.2) + mongo (~> 1.3) tzinfo (~> 0.3.22) will_paginate (~> 3.0.pre) net-ssh (2.0.24) @@ -205,7 +204,7 @@ GEM rake (>= 0.8.7) thor (~> 0.14.4) rake (0.8.7) - responders (0.6.2) + responders (0.6.4) rest-client (1.6.1) mime-types (>= 1.16) rmagick (2.12.2) @@ -276,7 +275,7 @@ DEPENDENCIES capybara cucumber (= 0.8.5) cucumber-rails - custom_fields (= 1.0.0.beta.10) + custom_fields (= 1.0.0.beta.12) database_cleaner delayed_job (= 2.1.4) delayed_job_mongoid (= 1.0.2) @@ -296,9 +295,9 @@ DEPENDENCIES locomotive_mongoid_acts_as_tree (= 0.1.5.5) mimetype-fu mocha! - mongoid (= 2.0.0.rc.7) + mongoid (~> 2.0.1) pickle - rails (>= 3.0.5) + rails (>= 3.0.7) rmagick (= 2.12.2) rspec-rails (= 2.3.1) ruby-debug diff --git a/README.textile b/README.textile index 483f4730..746b827b 100644 --- a/README.textile +++ b/README.textile @@ -19,8 +19,8 @@ h2. Gems Here is a short list of main gems used in the application. -* Rails 3.0.5 -* Mongoid 2.0.0.rc.7 (with MongoDB 1.6) +* Rails 3.0.7 +* Mongoid 2.0.1 (with MongoDB 1.6) * Liquid * Devise * Carrierwave @@ -38,6 +38,10 @@ h2. Team * Contributors: "Dirk Kelly":http://www.dirkkelly.com, "Mario Visic":http://www.mariovisic.com, "Raphael Costa":http://raphaelcosta.net (Brazilian Portuguese translation), "Bernd Hauser":http://www.designhunger.de (German translation), "Andrea Frigido":http://www.frisoft.it (Italian translation) * UI Designer: "Sacha Greif":http://www.sachagreif.com +h2. Support + +Bernd Hauser from "designhunger":http://www.designhunger.de funded the following feature: *has_one* / *has_many* between content types. + h2. Credits "Rodrigo Alvarez":http://blog.codecaster.es/ for his plugin named Congo which gave us a good starting point and for his availability for (very late) tech discussions. diff --git a/app/helpers/admin/content_types_helper.rb b/app/helpers/admin/content_types_helper.rb index aa6cd289..e59d06bc 100644 --- a/app/helpers/admin/content_types_helper.rb +++ b/app/helpers/admin/content_types_helper.rb @@ -7,7 +7,7 @@ module Admin::ContentTypesHelper @content_types = current_site.content_types.ordered. limit(:contents => Locomotive.config.lastest_items_nb). - only(:name, :slug, :highlighted_field_name, :content_custom_fields_version).to_a + only(:name, :slug, :highlighted_field_name, :content_custom_fields_version, :order_by).to_a if @content_type && @content_type.persisted? && @content_types.index(@content_type) >= MAX_DISPLAYED_CONTENTS @content_types.delete(@content_type) diff --git a/app/helpers/admin/custom_fields_helper.rb b/app/helpers/admin/custom_fields_helper.rb index 7766e21d..2c8a9d2f 100644 --- a/app/helpers/admin/custom_fields_helper.rb +++ b/app/helpers/admin/custom_fields_helper.rb @@ -1,7 +1,7 @@ module Admin::CustomFieldsHelper def options_for_field_kind - %w(string text category boolean date file).map do |kind| + %w(string text category boolean date file has_one has_many).map do |kind| [t("custom_fields.kind.#{kind}"), kind] end end @@ -39,4 +39,19 @@ module Admin::CustomFieldsHelper end end + def options_for_association_target + current_site.content_types.collect { |c| [c.name, c.content_klass.to_s] } + end + + def options_for_has_one(field) + target_contents_from_field(field).collect { |c| [c._label, c._id] } + end + + alias :options_for_has_many :options_for_has_one + + def target_contents_from_field(field) + content_type = field.target.constantize._parent + content_type.ordered_contents + end + end diff --git a/app/models/content_instance.rb b/app/models/content_instance.rb index a14f620e..70a55112 100644 --- a/app/models/content_instance.rb +++ b/app/models/content_instance.rb @@ -5,6 +5,7 @@ class ContentInstance ## extensions ## include CustomFields::ProxyClassEnabler + include Extensions::Shared::Seo ## fields (dynamic fields) ## field :_slug @@ -29,6 +30,8 @@ class ContentInstance ## methods ## + delegate :site, :to => :content_type + alias :visible? :_visible? alias :_permalink :_slug @@ -37,9 +40,11 @@ class ContentInstance end def highlighted_field_value - self.send(self.content_type.highlighted_field._name) + self.send(self.content_type.highlighted_field_name) end + alias :_label :highlighted_field_value + def visible? self._visible || self._visible.nil? end @@ -48,6 +53,14 @@ class ContentInstance Hash.new.replace(self.errors) end + def reload_parent! + self.class.reload_parent! + end + + def self.reload_parent! + self._parent = self._parent.reload + end + def to_liquid Locomotive::Liquid::Drops::Content.new(self) end diff --git a/app/models/extensions/shared/seo.rb b/app/models/extensions/shared/seo.rb new file mode 100644 index 00000000..06d190bc --- /dev/null +++ b/app/models/extensions/shared/seo.rb @@ -0,0 +1,13 @@ +module Extensions + module Shared + module Seo + extend ActiveSupport::Concern + + included do + field :meta_keywords, :type => String + field :meta_description, :type => String + end + + end # Seo + end # Shared +end # Extensions diff --git a/app/models/membership.rb b/app/models/membership.rb index 7e89b5d1..a3f3a6b7 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -6,7 +6,7 @@ class Membership field :admin, :type => Boolean, :default => false ## associations ## - referenced_in :account + referenced_in :account, :validate => false embedded_in :site, :inverse_of => :memberships ## validations ## diff --git a/app/models/page.rb b/app/models/page.rb index a64b45db..2a6a4125 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -10,6 +10,7 @@ class Page include Extensions::Page::Templatized include Extensions::Page::Redirect include Extensions::Page::Listed + include Extensions::Shared::Seo ## fields ## field :title diff --git a/app/views/admin/contents/_form.html.haml b/app/views/admin/contents/_form.html.haml index d79bacaf..745b8129 100644 --- a/app/views/admin/contents/_form.html.haml +++ b/app/views/admin/contents/_form.html.haml @@ -2,4 +2,9 @@ = include_javascripts :edit_custom_fields, :contents = include_stylesheets :fancybox += f.foldable_inputs :name => :meta do + + = f.input :meta_keywords + = f.input :meta_description + = render 'admin/custom_fields/custom_form', :form => f, :title => :attributes, :parent => @content_type \ No newline at end of file diff --git a/app/views/admin/custom_fields/_custom_form.html.haml b/app/views/admin/custom_fields/_custom_form.html.haml index d43766b4..92ed6b7c 100644 --- a/app/views/admin/custom_fields/_custom_form.html.haml +++ b/app/views/admin/custom_fields/_custom_form.html.haml @@ -4,35 +4,6 @@ - form.object.custom_fields.each do |field| - required = highlighted_field_name == field._name || field.required - - if field.string? - = form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :required => required - - - elsif field.text? - = form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :as => :text, :required => required, :input_html => { :class => field.text_formatting } - - - elsif field.category? - = form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle' do - = form.select field._name.to_sym, field.ordered_category_items.collect { |item| [item.name, item.id] } - %button.button.light.edit-categories-link{ :type => 'button', :'data-url' => edit_admin_custom_field_path(parent.class.model_name.underscore, parent.slug, field) } - %span!= t('.edit_categories') - - - elsif field.boolean? - = form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle' do - = form.check_box field._alias.to_sym - - - elsif field.date? - = form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :input_html => { :class => 'date' } - - - elsif field.file? - = form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'file' do - = form.file_field field._name.to_sym - - if form.object.send(:"#{field._name}?") - %p.remove - %strong - = link_to File.basename(form.object.send(field._name).url), form.object.send(field._name).url - %span -  /  - != t('.delete_file') - = form.check_box :"remove_#{field._name}" + = render "/admin/custom_fields/types/#{field.kind}", :form => form, :parent => parent, :field => field, :required => required = render '/admin/custom_fields/category_tmpl' \ No newline at end of file diff --git a/app/views/admin/custom_fields/_edit_field.html.haml b/app/views/admin/custom_fields/_edit_field.html.haml index 48cfd42c..7884f219 100644 --- a/app/views/admin/custom_fields/_edit_field.html.haml +++ b/app/views/admin/custom_fields/_edit_field.html.haml @@ -8,6 +8,7 @@ = g.input :_alias = g.input :hint = g.input :text_formatting, :as => 'select', :collection => options_for_text_formatting, :include_blank => false, :wrapper_html => { :style => 'display: none' } + = g.input :target, :as => 'select', :collection => options_for_association_target, :include_blank => false, :wrapper_html => { :style => 'display: none' } .popup-actions %p diff --git a/app/views/admin/custom_fields/_index.html.haml b/app/views/admin/custom_fields/_index.html.haml index 36dbe998..c7c60283 100644 --- a/app/views/admin/custom_fields/_index.html.haml +++ b/app/views/admin/custom_fields/_index.html.haml @@ -23,6 +23,8 @@ %input{ :name => '{{base_name}}[text_formatting]', :value => '{{{text_formatting}}}', :type => 'hidden', :'data-field' => 'text_formatting' } + %input{ :name => '{{base_name}}[target]', :value => '{{{target}}}', :type => 'hidden', :'data-field' => 'target' } + %input{ :name => '{{base_name}}[label]', :value => '{{{label}}}', :type => 'text', :'data-field' => 'label' } — diff --git a/app/views/admin/custom_fields/types/_boolean.html.haml b/app/views/admin/custom_fields/types/_boolean.html.haml new file mode 100644 index 00000000..2ad89277 --- /dev/null +++ b/app/views/admin/custom_fields/types/_boolean.html.haml @@ -0,0 +1,2 @@ += form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle' do + = form.check_box field._alias.to_sym diff --git a/app/views/admin/custom_fields/types/_category.html.haml b/app/views/admin/custom_fields/types/_category.html.haml new file mode 100644 index 00000000..e0ec624d --- /dev/null +++ b/app/views/admin/custom_fields/types/_category.html.haml @@ -0,0 +1,4 @@ += form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle' do + = form.select field._name.to_sym, field.ordered_category_items.collect { |item| [item.name, item.id] } + %button.button.light.edit-categories-link{ :type => 'button', :'data-url' => edit_admin_custom_field_path(parent.class.model_name.underscore, parent.slug, field) } + %span!= t('.edit_categories') diff --git a/app/views/admin/custom_fields/types/_date.html.haml b/app/views/admin/custom_fields/types/_date.html.haml new file mode 100644 index 00000000..445f2e63 --- /dev/null +++ b/app/views/admin/custom_fields/types/_date.html.haml @@ -0,0 +1 @@ += form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :input_html => { :class => 'date' } \ No newline at end of file diff --git a/app/views/admin/custom_fields/types/_file.html.haml b/app/views/admin/custom_fields/types/_file.html.haml new file mode 100644 index 00000000..955291f6 --- /dev/null +++ b/app/views/admin/custom_fields/types/_file.html.haml @@ -0,0 +1,10 @@ += form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'file' do + = form.file_field field._name.to_sym + - if form.object.send(:"#{field._name}?") + %p.remove + %strong + = link_to File.basename(form.object.send(field._name).url), form.object.send(field._name).url + %span +  /  + != t('.delete_file') + = form.check_box :"remove_#{field._name}" diff --git a/app/views/admin/custom_fields/types/_has_many.html.haml b/app/views/admin/custom_fields/types/_has_many.html.haml new file mode 100644 index 00000000..b167ebc5 --- /dev/null +++ b/app/views/admin/custom_fields/types/_has_many.html.haml @@ -0,0 +1,33 @@ +- field.target.constantize.reload_parent! # to make sure all the contents from the parent are loaded + += form.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'has-many' do + + .has-many-selector + + %p{ :style => form.object.send(field._alias.to_sym).empty? ? '' : 'display: none' } + = t('.empty') + + %ul + + %script{ :type => 'text/x-mustache-template', :name => 'template', :'data-base-input-name' => "content[#{field._alias.to_sym}]" } + %li{ :class => "item {{behaviour_flag}}" } + %span.handle + = image_tag 'admin/form/icons/drag.png' + + {{^if_template}} + %input{ :name => '{{base_name}}[]', :value => '{{{id}}}', :type => 'hidden', :'data-field' => 'id' } + {{/if_template}} + + %strong {{label}} + + {{#if_template}} + = select_tag 'label', '' + {{/if_template}} + + %span.actions + = link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove' + %button{ :class => 'button light mini add', :type => 'button' } + %span!= t('admin.buttons.new_item') + + %script{ :type => 'text/javascript', :name => 'data' } + != collection_to_js(options_for_has_many(field), :taken_ids => form.object.send(field._alias.to_sym).ids) diff --git a/app/views/admin/custom_fields/types/_has_one.html.haml b/app/views/admin/custom_fields/types/_has_one.html.haml new file mode 100644 index 00000000..86995ef3 --- /dev/null +++ b/app/views/admin/custom_fields/types/_has_one.html.haml @@ -0,0 +1,4 @@ +- field.target.constantize.reload_parent! # to make sure all the contents from the parent are loaded + += form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :input_html => { :class => 'has_one' }, :as => :select, :collection => options_for_has_one(field), :selected => form.object.send(field._alias.to_sym).try(:_id) + diff --git a/app/views/admin/custom_fields/types/_string.html.haml b/app/views/admin/custom_fields/types/_string.html.haml new file mode 100644 index 00000000..d35db032 --- /dev/null +++ b/app/views/admin/custom_fields/types/_string.html.haml @@ -0,0 +1 @@ += form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :required => required \ No newline at end of file diff --git a/app/views/admin/custom_fields/types/_text.html.haml b/app/views/admin/custom_fields/types/_text.html.haml new file mode 100644 index 00000000..b4f6192d --- /dev/null +++ b/app/views/admin/custom_fields/types/_text.html.haml @@ -0,0 +1 @@ += form.input field._alias.to_sym, :label => field.label, :hint => field.hint, :as => :text, :required => required, :input_html => { :class => field.text_formatting } \ No newline at end of file diff --git a/app/views/admin/pages/_form.html.haml b/app/views/admin/pages/_form.html.haml index 6db15972..34ed29bd 100644 --- a/app/views/admin/pages/_form.html.haml +++ b/app/views/admin/pages/_form.html.haml @@ -11,6 +11,10 @@ = f.input :slug, :required => false, :hint => @page.slug.blank? ? ' ' : page_url(@page), :input_html => { :data_url => get_path_admin_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized?}; height: 50px" } += f.foldable_inputs :name => :meta do + + = f.input :meta_keywords + = f.input :meta_description = f.foldable_inputs :name => :advanced_options do diff --git a/config/assets.yml b/config/assets.yml index c42bd1d3..d2943364 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -34,6 +34,7 @@ javascripts: - public/javascripts/admin/plugins/fancybox.js - public/javascripts/admin/plugins/mustache.js - public/javascripts/admin/custom_fields/category.js + - public/javascripts/admin/custom_fields/has_many.js asset_collections: - public/javascripts/admin/plugins/fancybox.js - public/javascripts/admin/plugins/mustache.js diff --git a/config/locales/formtastic.en.yml b/config/locales/formtastic.en.yml index c8baf61d..0e11199f 100644 --- a/config/locales/formtastic.en.yml +++ b/config/locales/formtastic.en.yml @@ -50,10 +50,12 @@ en: templatized: "Use the page as a template for a model you defined." listed: "Control whether to show the page from generated menus." content_type_id: "The type of content this page will be a template for." + meta_keywords: "Overrides the site's meta keywords used within the head tag of the page. They are separated by a comma." + meta_description: "Overrides the site's meta description used within the head tag of the page." snippet: slug: "You need to know it in order to insert the snippet inside a page" site: - meta_keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO." + meta_keywords: "Meta keywords used within the head tag of the page. They are separated by a comma. Required for SEO." meta_description: "Meta description used within the head tag of the page. Required for SEO." domain_name: "ex: locomotiveapp.org" theme_asset: diff --git a/config/locales/formtastic.fr.yml b/config/locales/formtastic.fr.yml index e065a7d5..99e98bf6 100644 --- a/config/locales/formtastic.fr.yml +++ b/config/locales/formtastic.fr.yml @@ -53,10 +53,12 @@ fr: templatized: "Utilise la page comme un template pour un modèle défini." listed: "Controle si la page doit être visible depuis les menus automatiquement générés." content_type_id: "Le type du contenu pour lequel cette page est un template." + meta_keywords: "Redéfinit les mots-clés du site. Utilisés à l'intérieur de la balise HEAD. Ils sont séparés par une virgule." + meta_description: "Redéfinit la description du site. Utilisée à l'intérieur de la balise HEAD." snippet: slug: "Utilisé pour insérer le snippet dans une page." site: - meta_keywords: "Mots-clés utilisés à l'intérieur de la balise HEAD. Ils sont séparés par un espace. Requis pour un meilleur référencement." + meta_keywords: "Mots-clés utilisés à l'intérieur de la balise HEAD. Ils sont séparés par une virgule. Requis pour un meilleur référencement." meta_description: "Description utilisée à l'intérieur de la balise HEAD. Requis pour un meilleur référencement." domain_name: "ex: locomotiveapp.org" theme_asset: diff --git a/lib/locomotive/liquid/drops/content.rb b/lib/locomotive/liquid/drops/content.rb index 19715b41..5aafcf75 100644 --- a/lib/locomotive/liquid/drops/content.rb +++ b/lib/locomotive/liquid/drops/content.rb @@ -2,6 +2,7 @@ module Locomotive module Liquid module Drops class Content < Base + delegate :meta_keywords, :meta_description, :to => "@source" def before_method(meth) return '' if @source.nil? diff --git a/lib/locomotive/liquid/drops/page.rb b/lib/locomotive/liquid/drops/page.rb index ea7e0e2c..a6b092a8 100644 --- a/lib/locomotive/liquid/drops/page.rb +++ b/lib/locomotive/liquid/drops/page.rb @@ -2,6 +2,7 @@ module Locomotive module Liquid module Drops class Page < Base + delegate :meta_keywords, :meta_description, :to => "@source" def title @source.templatized? ? @context['content_instance'].highlighted_field_value : @source.title diff --git a/lib/locomotive/liquid/tags/seo_metadata.rb b/lib/locomotive/liquid/tags/seo_metadata.rb index c170729b..5958b47d 100644 --- a/lib/locomotive/liquid/tags/seo_metadata.rb +++ b/lib/locomotive/liquid/tags/seo_metadata.rb @@ -5,8 +5,8 @@ module Locomotive def render(context) %{ - - + + } end @@ -15,6 +15,19 @@ module Locomotive string.strip.gsub(/"/, '') end + def meta_description(context) + object = metadata_object(context) + object.try(:meta_description).blank? ? context.registers[:site].meta_description : object.meta_description + end + + def meta_keywords(context) + object = metadata_object(context) + object.try(:meta_keywords).blank? ? context.registers[:site].meta_keywords : object.meta_keywords + end + + def metadata_object(context) + context['content_instance'] || context['page'] + end end ::Liquid::Template.register_tag('seo_metadata', SEOMetadata) diff --git a/locomotive_cms.gemspec b/locomotive_cms.gemspec index 9e1ca89b..56061bf6 100644 --- a/locomotive_cms.gemspec +++ b/locomotive_cms.gemspec @@ -17,10 +17,10 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "nowarning" - s.add_dependency "rails", ">= 3.0.6" + s.add_dependency "rails", ">= 3.0.7" s.add_dependency "warden" s.add_dependency "devise", "1.1.3" - s.add_dependency "mongoid", "2.0.0.rc.7" + s.add_dependency "mongoid", "2.0.1" s.add_dependency "bson_ext", "~> 1.3.0" s.add_dependency "locomotive_mongoid_acts_as_tree", "0.1.5.5" s.add_dependency "will_paginate" @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.add_dependency "rmagick", "2.12.2" s.add_dependency "locomotive_carrierwave", "0.5.0.1.beta3" - s.add_dependency "custom_fields", "1.0.0.beta.10" + s.add_dependency "custom_fields", "1.0.0.beta.12" s.add_dependency "fog", "0.3.7" s.add_dependency "mimetype-fu" s.add_dependency "actionmailer-with-request" diff --git a/public/javascripts/admin/application.js b/public/javascripts/admin/application.js index 1dcfceec..00093fed 100644 --- a/public/javascripts/admin/application.js +++ b/public/javascripts/admin/application.js @@ -80,7 +80,7 @@ $(document).ready(function() { $('#submenu > ul > li.' + css).addClass('on'); // form - $('.formtastic li input, .formtastic li textarea, .formtastic li code').focus(function() { + $('.formtastic li input, .formtastic li textarea, .formtastic li code, .formtastic li select').focus(function() { $('.formtastic li.error:not(.code) p.inline-errors').fadeOut(200); if ($(this).parent().hasClass('error')) { $(this).nextAll('p.inline-errors').show(); diff --git a/public/javascripts/admin/custom_fields.js b/public/javascripts/admin/custom_fields.js index 0b7715b2..fd9e4b64 100644 --- a/public/javascripts/admin/custom_fields.js +++ b/public/javascripts/admin/custom_fields.js @@ -50,7 +50,7 @@ $(document).ready(function() { // edit domField.find('a.edit').click(function(e) { var link = $(this); - var attributes = ['_alias', 'hint', 'text_formatting']; + var attributes = ['_alias', 'hint', 'text_formatting', 'target']; $.fancybox({ titleShow: false, @@ -76,6 +76,8 @@ $(document).ready(function() { domBoxAttrVal(name, val); }); if (domFieldVal(domField, 'kind').toLowerCase() == 'text') domBoxAttr('text_formatting').parents('li').show(); + if (domFieldVal(domField, 'kind').toLowerCase() == 'has_one' || + domFieldVal(domField, 'kind').toLowerCase() == 'has_many') domBoxAttr('target').parents('li').show(); } }); e.preventDefault(); e.stopPropagation(); @@ -200,161 +202,5 @@ $(document).ready(function() { } } - setup(); // <- let's the show begin -}); - -// $(document).ready(function() { -// -// $('fieldset.fields').parents('form').submit(function() { -// $('fieldset.fields li.template input, fieldset.fields li.template select').attr('disabled', 'disabled'); -// }); -// -// var defaultValue = $('fieldset.fields li.template input[type=text]').val(); -// var selectOnChange = function(select) { -// select.hide(); -// select.prev() -// .show() -// .html(select[0].options[select[0].options.selectedIndex].text); -// } -// -// var refreshPosition = function() { -// jQuery.each($('fieldset.fields li.added input.position'), function(index) { -// $(this).val(index); -// }); -// } -// -// /* __ fields ___ */ -// $('fieldset.fields li.added select').each(function() { -// var select = $(this) -// .hover(function() { -// clearTimeout(select.attr('timer')); -// }, function() { -// select.attr('timer', setTimeout(function() { -// select.hide(); -// select.prev().show(); -// }, 1000)); -// }) -// .change(function() { selectOnChange(select); }); -// -// select.prev().click(function() { -// $(this).hide(); -// select.show(); -// }); -// }); -// -// $('fieldset.fields li.template input[type=text]').focus(function() { -// if ($(this).hasClass('void') && $(this).parents('li').hasClass('template')) -// $(this).val('').removeClass('void'); -// }); -// -// $('fieldset.fields li.template button').click(function() { -// var lastRow = $(this).parents('li.template'); -// -// var label = lastRow.find('input.label').val().trim(); -// if (label == '' || label == defaultValue) return false; -// -// var newRow = lastRow.clone(true).removeClass('template').addClass('added new').insertBefore(lastRow); -// -// var dateFragment = '[' + new Date().getTime() + ']'; -// newRow.find('input, select').each(function(index) { -// $(this).attr('name', $(this).attr('name').replace('[-1]', dateFragment)); -// }); -// -// var select = newRow.find('select') -// .val(lastRow.find('select').val()) -// .change(function() { selectOnChange(select); }) -// .hover(function() { -// clearTimeout(select.attr('timer')); -// }, function() { -// select.attr('timer', setTimeout(function() { -// select.hide(); -// select.prev().show(); -// }, 1000)); -// }); -// select.prev() -// .html(select[0].options[select[0].options.selectedIndex].text) -// .click(function() { -// $(this).hide(); -// select.show(); -// }); -// -// // then "reset" the form -// lastRow.find('input.label').val(defaultValue).addClass('void'); -// -// // warn the sortable widget about the new row -// $("fieldset.fields ol").sortable('refresh'); -// -// refreshPosition(); -// }); -// -// $('fieldset.fields li a.remove').click(function(e) { -// if (confirm($(this).attr('data-confirm'))) { -// var parent = $(this).parents('li'); -// -// if (parent.hasClass('new')) -// parent.remove(); -// else { -// var field = parent.find('input.position') -// field.attr('name', field.attr('name').replace('[position]', '[_destroy]')); -// -// parent.hide().removeClass('added') -// } -// -// refreshPosition(); -// } -// -// e.preventDefault(); -// e.stopPropagation(); -// }); -// -// // sortable list -// $("fieldset.fields ol").sortable({ -// handle: 'span.handle', -// items: 'li:not(.template)', -// axis: 'y', -// update: refreshPosition -// }); -// -// // edit in depth custom field -// $('fieldset.fields li.item span.actions a.edit').click(function() { -// var link = $(this); -// $.fancybox({ -// titleShow: false, -// content: $(link.attr('href')).parent().html(), -// onComplete: function() { -// $('#fancybox-wrap form').submit(function(e) { -// $.fancybox.close(); -// e.preventDefault(); -// e.stopPropagation(); -// }); -// -// var parent = link.parent(); -// -// if (parent.prevAll('select').val() == 'Text') { -// var formatting = parent.prevAll('.text-formatting').val(); -// $('#fancybox-wrap #custom_fields_field_text_formatting').val(formatting); -// $('#fancybox-wrap #custom_fields_field_text_formatting_input').show(); -// } else { -// $('#fancybox-wrap #custom_fields_field_text_formatting_input').hide(); -// } -// -// var alias = parent.prevAll('.alias').val().trim(); -// if (alias == '') alias = makeSlug(link.parent().prevAll('.label').val()); -// $('#fancybox-wrap #custom_fields_field__alias').val(alias); -// -// var hint = parent.prevAll('.hint').val(); -// $('#fancybox-wrap #custom_fields_field_hint').val(hint); -// }, -// onCleanup: function() { -// var parent = link.parent(); -// -// var alias = $('#fancybox-wrap #custom_fields_field__alias').val().trim(); -// if (alias != '') parent.prevAll('.alias').val(alias); -// var hint = $('#fancybox-wrap #custom_fields_field_hint').val().trim(); -// if (hint != '') parent.prevAll('.hint').val(hint); -// var formatting = $('#fancybox-wrap #custom_fields_field_text_formatting').val(); -// parent.prevAll('.text-formatting').val(formatting); -// } -// }) -// }); -// }); + setup(); // <- let the show begin +}); \ No newline at end of file diff --git a/public/javascripts/admin/custom_fields/has_many.js b/public/javascripts/admin/custom_fields/has_many.js new file mode 100644 index 00000000..c66001ee --- /dev/null +++ b/public/javascripts/admin/custom_fields/has_many.js @@ -0,0 +1,161 @@ +$(document).ready(function() { + // add/remove/sort items in a has_many relationship + $('.has-many-selector').hasManySelector(); +}); + +(function($){ + $.fn.hasManySelector = function(options) { + + var populateSelect = function(context) { + context.select.find('option').remove(); + + for (var i = 0; i < context.data.collection.length; i++) { + var obj = context.data.collection[i]; + if ($.inArray(obj[1], context.data.taken_ids) == -1) + context.select.append(new Option(obj[0], obj[1], true, true)); + } + + if (context.select.find('option').size() == 0) + context.list.find('li.template').hide(); + else + context.list.find('li.template').show(); + } + + var addId = function(context, id) { + context.data.taken_ids.push(id); + + populateSelect(context); + + if (context.data.taken_ids.length > 0) { + context.empty.hide(); + context.list.next('input[type=hidden]').remove(); + } + + if (context.data.taken_ids.length == context.data.collection.length) + context.sep.hide(); + } + + var removeId = function(context, id) { + context.data.taken_ids = jQuery.grep(context.data.taken_ids, function(value) { + return value != id; + }); + + populateSelect(context); + + if (context.data.taken_ids.length == 0) { + context.empty.show(); + context.list.after(''); + } + + context.sep.show(); + } + + var registerElementEvents = function(context, data, domElement) { + // remove + domElement.find('a.remove').click(function(e) { + domElement.remove(); + + removeId(context, data.id); + + context.list.sortable('refresh'); + + e.preventDefault(); e.stopPropagation(); + }); + } + + var registerElementTemplateEvents = function(context, domElement) { + // bind the "Add field" button + domElement.find('button').click(function(e) { + var newElement = { + id: context.select.val(), + label: context.select.find('option:selected').text() + }; + + addId(context, newElement.id); + + addElement(context, newElement, { refreshPosition: true }); + + context.list.sortable('refresh'); + + e.preventDefault(); e.stopPropagation(); + }); + } + + /* ___ Add an element into the list ___ */ + var addElement = function(context, data, options) { + options = $.extend({ + 'is_template': false, + 'refreshPosition': false + }, options); + + data = $.extend({ + behaviour_flag: function() { return options.is_template ? 'template' : 'added' }, + base_name: function() { return options.is_template ? '' : context.baseInputName }, + if_template: function() { return options.is_template } + }, data); + + var html = Mustache.to_html(context.template, data); + + var domElement = null; + + if (options.is_template) { + domElement = context.list.append('
  •  
  • ').append(html).find('.template'); + + context.sep = context.list.find('.sep'); + + registerElementTemplateEvents(context, domElement); + } + else { + domElement = context.list.find('> .sep').before(html).prev('li'); + + registerElementEvents(context, data, domElement); + + context.error.hide(); + + context.list.sortable('refresh'); + } + } + + return this.each(function() { + var wrapper = $(this); + + var context = { + list: wrapper.find('ul'), + empty: wrapper.find('p:first'), + template: wrapper.find('script[name=template]').html(), + baseInputName: wrapper.find('script[name=template]').attr('data-base-input-name'), + data: eval(wrapper.find('script[name=data]').html()), + error: wrapper.parent().find('p.inline-errors') + }; + + // sortable list + context.list.sortable({ + handle: 'span.handle', + items: 'li:not(.template)', + axis: 'y' + }); + + // add the template element used to insert the new ones + addElement(context, null, { is_template: true }); + + context.select = wrapper.find('select[name=label]'); + populateSelect(context); + + for (var i = 0; i < context.data.taken_ids.length; i++) { + var data = { id: context.data.taken_ids[i], label: null }; + + for (var j = 0; j < context.data.collection.length; j++) { + var current = context.data.collection[j]; + if (data.id == current[1]) + data.label = current[0]; + } + + addElement(context, data); + } + + if (context.error.size() > 0) + context.error.show(); + }); + }; +})(jQuery); + diff --git a/public/stylesheets/admin/buttons.css b/public/stylesheets/admin/buttons.css index 0e88c56a..169bc603 100644 --- a/public/stylesheets/admin/buttons.css +++ b/public/stylesheets/admin/buttons.css @@ -1,3 +1,7 @@ +/* ___ AUTOMATICALLY GENERATED: see admin/button.scss for the source file */ +/* ___ rounded ___ */ +/* ___ box shadow ___ */ +/* ___ others ___ */ .button { display: inline-block; background: transparent url(/images/admin/buttons/dark-gray-left.png) no-repeat 0 0; @@ -7,49 +11,51 @@ cursor: pointer; border: none; height: 31px; - outline: none; -} - -.button span { - display: inline-block; - background: transparent url(/images/admin/buttons/dark-gray-right.png) no-repeat right top; - position: relative; - top: -1px; - padding: 3px 9px 9px 4px; - line-height: 21px; - text-shadow: 1px 1px 1px #000; - outline: none; -} - -.button.light { - background-image: url(/images/admin/buttons/light-gray-left.png); - color: #787a89; -} - -.button.light span { - background-image: url(/images/admin/buttons/light-gray-right.png); - text-shadow: 1px 1px 1px #fff; -} - -.button.small { - background: #ebedf4; - outline: none; - -moz-border-radius : 10px; - -webkit-border-radius: 10px; - height: 20px; - font-size: 0.7em; - padding: 0px 12px 0px 12px; - color: #8B8D9A !important; - text-decoration: none; - text-shadow: 1px 1px 1px #fff; -} - -.button.small.add { -} - -.button.remove { - color: #ff092c !important; - font-size: 1.1em; -} - -.button.remove:hover { text-decoration: underline; } + outline: none; } + .button span { + display: inline-block; + background: transparent url(/images/admin/buttons/dark-gray-right.png) no-repeat right top; + position: relative; + top: -1px; + padding: 3px 9px 9px 4px; + line-height: 21px; + text-shadow: 1px 1px 1px #000; + outline: none; } + .button.light { + background-image: url(/images/admin/buttons/light-gray-left.png); + color: #787a89; } + .button.light span { + background-image: url(/images/admin/buttons/light-gray-right.png); + text-shadow: 1px 1px 1px #fff; } + .button.small { + background: #ebedf4; + outline: none; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + height: 20px; + font-size: 0.7em; + padding: 0px 12px 0px 12px; + color: #8B8D9A !important; + text-decoration: none; + text-shadow: 1px 1px 1px #fff; } + .button.mini.add { + background: transparent; + height: 20px; + background: #e1e4ed; + background: -moz-linear-gradient(0% 100% 90deg, #d7dbe7, #ebedf4); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ebedf4), to(#d7dbe7)); + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); + -moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); } + .button.mini.add span { + background: none; + line-height: 10px; + padding: 0px 5px 0 0; } + .button.remove { + color: #ff092c !important; + font-size: 1.1em; } + .button.remove:hover { + text-decoration: underline; } diff --git a/public/stylesheets/admin/formtastic_changes.css b/public/stylesheets/admin/formtastic_changes.css index ce696761..44f279cb 100644 --- a/public/stylesheets/admin/formtastic_changes.css +++ b/public/stylesheets/admin/formtastic_changes.css @@ -434,6 +434,85 @@ form.formtastic fieldset ol li.file p.remove a { form.formtastic fieldset ol li.file p.remove a:hover { text-decoration: underline; } +form.formtastic fieldset ol li.has-many p { + font-style: italic; + margin-bottom: 5px; +} + +form.formtastic fieldset ol li.has-many ul { + width: 736px; + margin-left: 20%; +} + +form.formtastic fieldset ol li.has-many ul li.item { + background: transparent url(/images/admin/form/item-popup.png) no-repeat 0 0; + width: 413px; + margin: 0 0 10px 0; +} + +form.formtastic fieldset ol li.has-many ul li.item strong { + font-size: 0.9em; + font-weight: bold; + color: #17171d; + position: relative; + top: -1px; + left: 10px; +} + +form.formtastic fieldset ol li.has-many ul li.item span.handle { + cursor: move; + position: relative; + top: 1px; +} + +form.formtastic fieldset ol li.has-many ul li.added span.actions a.remove { + display: inline; +} + +form.formtastic fieldset ol li.has-many ul li.added span.actions button { + display: none; +} + +form.formtastic fieldset ol li.has-many ul li.sep { + border-top: 1px solid #ccc; + height: 2px; + margin: 5px 0 7px; + width: 433px; +} + +form.formtastic fieldset ol li.has-many ul li.template {} + +form.formtastic fieldset ol li.has-many ul li.template select { + display: inline; + top: -1px; +} + +form.formtastic fieldset ol li.has-many ul li.template span.handle { + display: none; +} + +form.formtastic fieldset ol li.has-many ul li.template span.actions { + width: auto; + top: 0px; +} + +form.formtastic fieldset ol li.has-many ul li.template span.actions a.remove { + display: none; +} + +form.formtastic fieldset ol li.has-many ul li.template span.actions button { + display: inline; + position: relative; + top: 1px; + right: 3px; +} + +form.formtastic fieldset ol li.has-many ul li.template span.actions button span { + font-size: 0.8em; +} + + + /* ___ my account ___ */ form.formtastic fieldset.language li.full span { diff --git a/public/stylesheets/sass/admin/buttons.scss b/public/stylesheets/sass/admin/buttons.scss new file mode 100644 index 00000000..bbcefeb1 --- /dev/null +++ b/public/stylesheets/sass/admin/buttons.scss @@ -0,0 +1,126 @@ +/* ___ AUTOMATICALLY GENERATED: see admin/button.scss for the source file */ + +@import "helpers"; + +.button { + display: inline-block; + background: transparent url(/images/admin/buttons/dark-gray-left.png) no-repeat 0 0; + padding: 0px 0px 0px 2px; + font-size: 0.9em; + color: white; + cursor: pointer; + border: none; + height: 31px; + outline: none; + + span { + display: inline-block; + background: transparent url(/images/admin/buttons/dark-gray-right.png) no-repeat right top; + position: relative; + top: -1px; + padding: 3px 9px 9px 4px; + line-height: 21px; + text-shadow: 1px 1px 1px #000; + outline: none; + } + + &.light { + background-image: url(/images/admin/buttons/light-gray-left.png); + color: #787a89; + + span { + background-image: url(/images/admin/buttons/light-gray-right.png); + text-shadow: 1px 1px 1px #fff; + } + } + + &.small { + background: #ebedf4; + outline: none; + -moz-border-radius : 10px; + -webkit-border-radius: 10px; + height: 20px; + font-size: 0.7em; + padding: 0px 12px 0px 12px; + color: #8B8D9A !important; + text-decoration: none; + text-shadow: 1px 1px 1px #fff; + } + + &.mini.add { + background: transparent; + height: 20px; + @include linear-background-gradient(#ebedf4, #d7dbe7); + @include full-rounded(4px); + @include box-shadow(1px, 1px, 1px, rgba(0, 0, 0, 0.4)); + + span { + background: none; + line-height: 10px; + padding: 0px 5px 0 0; + } + } + + &.remove { + color: #ff092c !important; + font-size: 1.1em; + + &:hover { text-decoration: underline; } + } +} + +// .button { +// display: inline-block; +// background: transparent url(/images/admin/buttons/dark-gray-left.png) no-repeat 0 0; +// padding: 0px 0px 0px 2px; +// font-size: 0.9em; +// color: white; +// cursor: pointer; +// border: none; +// height: 31px; +// outline: none; +// } +// +// .button span { +// display: inline-block; +// background: transparent url(/images/admin/buttons/dark-gray-right.png) no-repeat right top; +// position: relative; +// top: -1px; +// padding: 3px 9px 9px 4px; +// line-height: 21px; +// text-shadow: 1px 1px 1px #000; +// outline: none; +// } +// +// .button.light { +// background-image: url(/images/admin/buttons/light-gray-left.png); +// color: #787a89; +// } +// +// .button.light span { +// background-image: url(/images/admin/buttons/light-gray-right.png); +// text-shadow: 1px 1px 1px #fff; +// } +// +// .button.small { +// background: #ebedf4; +// outline: none; +// -moz-border-radius : 10px; +// -webkit-border-radius: 10px; +// height: 20px; +// font-size: 0.7em; +// padding: 0px 12px 0px 12px; +// color: #8B8D9A !important; +// text-decoration: none; +// text-shadow: 1px 1px 1px #fff; +// } +// +// .button.small.add { +// } +// +// .button.remove { +// color: #ff092c !important; +// font-size: 1.1em; +// } +// +// .button.remove:hover { text-decoration: underline; } diff --git a/spec/lib/locomotive/liquid/drops/content_spec.rb b/spec/lib/locomotive/liquid/drops/content_spec.rb new file mode 100644 index 00000000..624cff4c --- /dev/null +++ b/spec/lib/locomotive/liquid/drops/content_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Locomotive::Liquid::Drops::Content do + + before(:each) do + @site = Factory.build(:site) + content_type = Factory.build(:content_type) + content_type.content_custom_fields.build :label => 'anything', :kind => 'String' + @content = content_type.contents.build(:meta_keywords => 'Libidinous, Angsty', :meta_description => "Quite the combination.") + end + + describe 'meta_keywords' do + subject { render_template('{{ content.meta_keywords }}') } + it { should == @content.meta_keywords } + end + + describe 'meta_description' do + subject { render_template('{{ content.meta_description }}') } + it { should == @content.meta_description } + end + + def render_template(template = '', assigns = {}) + assigns = { 'content' => @content }.merge(assigns) + Liquid::Template.parse(template).render(::Liquid::Context.new({}, assigns, { :site => @site })) + end + +end diff --git a/spec/lib/locomotive/liquid/drops/page_spec.rb b/spec/lib/locomotive/liquid/drops/page_spec.rb index ec53c262..b80328cd 100644 --- a/spec/lib/locomotive/liquid/drops/page_spec.rb +++ b/spec/lib/locomotive/liquid/drops/page_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe Locomotive::Liquid::Drops::Page do before(:each) do - @home = Factory.build(:page) + site = Factory.build(:site) + @home = Factory.build(:page, :site => site, :meta_keywords => 'Libidinous, Angsty', :meta_description => "Quite the combination.") end context '#rendering tree' do @@ -56,6 +57,16 @@ describe Locomotive::Liquid::Drops::Page do end + describe 'meta_keywords' do + subject { render_template('{{ home.meta_keywords }}') } + it { should == @home.meta_keywords } + end + + describe 'meta_description' do + subject { render_template('{{ home.meta_description }}') } + it { should == @home.meta_description } + end + def render_template(template = '', assigns = {}) assigns = { 'home' => @home diff --git a/spec/lib/locomotive/liquid/tags/seo_metadata_spec.rb b/spec/lib/locomotive/liquid/tags/seo_metadata_spec.rb index fe7200c5..5a5eeea3 100644 --- a/spec/lib/locomotive/liquid/tags/seo_metadata_spec.rb +++ b/spec/lib/locomotive/liquid/tags/seo_metadata_spec.rb @@ -1,19 +1,17 @@ require 'spec_helper' describe Locomotive::Liquid::Tags::SEOMetadata do - - before :each do - @site = Factory.build(:site, :meta_description => 'A short site description', :meta_keywords => 'test only cat dog') + let(:site) do + Factory.build(:site, :meta_description => 'A short site description', :meta_keywords => 'test only cat dog') end - context '#rendering' do - + describe 'rendering' do it 'renders a a meta description tag' do render_seo_metadata.should include '' end it 'strips and removes quote characters from the description' do - @site.meta_description = ' String with " " quotes ' + site.meta_description = ' String with " " quotes ' render_seo_metadata.should include '' end @@ -22,16 +20,53 @@ describe Locomotive::Liquid::Tags::SEOMetadata do end it 'strips and removes quote characters from the keywords' do - @site.meta_keywords = ' one " two " three ' + site.meta_keywords = ' one " two " three ' render_seo_metadata.should include '' end + + context "when page" do + context "has metadata" do + let(:page) { site.pages.build(:meta_keywords => 'hulk,gamma', :meta_description => "Bruce Banner") } + subject { render_seo_metadata('page' => page) } + it { should include(%Q[]) } + it { should include(%Q[]) } + end + context "does not have metadata" do + let(:page) { site.pages.build } + subject { render_seo_metadata('page' => page) } + it { should include(%Q[]) } + it { should include(%Q[]) } + end + end + + context "when content instance" do + let(:content_type) do + Factory.build(:content_type, :site => site).tap do |ct| + ct.content_custom_fields.build :label => 'anything', :kind => 'String' + end + end + + context "has metadata" do + let(:content) { content_type.contents.build(:meta_keywords => 'Libidinous, Angsty', :meta_description => "Quite the combination.") } + subject { render_seo_metadata('content_instance' => content) } + it { should include(%Q[]) } + it { should include(%Q[]) } + end + + context "does not have metadata" do + let(:content) { content_type.contents.build } + subject { render_seo_metadata('content_instance' => content) } + it { should include(%Q[]) } + it { should include(%Q[]) } + end + end end + - def render_seo_metadata - registers = { :site => @site } - liquid_context = ::Liquid::Context.new({}, {}, registers) + def render_seo_metadata(assigns={}) + registers = { :site => site } + liquid_context = ::Liquid::Context.new({}, assigns, registers) output = Liquid::Template.parse("{% seo_metadata %}").render(liquid_context) end - end \ No newline at end of file diff --git a/spec/models/content_instance_spec.rb b/spec/models/content_instance_spec.rb index 1585ac24..987281f4 100644 --- a/spec/models/content_instance_spec.rb +++ b/spec/models/content_instance_spec.rb @@ -93,6 +93,13 @@ describe ContentInstance do end end + + describe '#site' do + it 'delegates to the content type' do + @content_type.expects(:site) + build_content.site + end + end def build_content(options = {}) @content_type.contents.build({ :title => 'Locomotive', :description => 'Lorem ipsum....' }.merge(options)) @@ -101,5 +108,4 @@ describe ContentInstance do def fake_bson_id(id) BSON::ObjectId(id.to_s.rjust(24, '0')) end - -end +end \ No newline at end of file diff --git a/spec/models/page_spec.rb b/spec/models/page_spec.rb index 11ecf9d6..9d896aa8 100644 --- a/spec/models/page_spec.rb +++ b/spec/models/page_spec.rb @@ -229,8 +229,6 @@ describe Page do @page.redirect_url = "invalid url with spaces" @page.should_not be_valid @page.errors[:redirect_url].should == ["is invalid"] - end - end end