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('