diff --git a/app/controllers/admin/contents_controller.rb b/app/controllers/admin/contents_controller.rb index 0454ef83..8fe2813c 100644 --- a/app/controllers/admin/contents_controller.rb +++ b/app/controllers/admin/contents_controller.rb @@ -12,13 +12,11 @@ module Admin end def create - @content = @content_type.contents.create(params[:content]) - - respond_with(@content, :location => edit_admin_content_url(@content_type.slug, @content)) + create! { edit_admin_content_url(@content_type.slug, @content.id) } end def update - update! { edit_admin_content_url(@content_type.slug, @content) } + update! { edit_admin_content_url(@content_type.slug, @content.id) } end def sort diff --git a/app/helpers/admin/base_helper.rb b/app/helpers/admin/base_helper.rb index f613d999..099b31be 100644 --- a/app/helpers/admin/base_helper.rb +++ b/app/helpers/admin/base_helper.rb @@ -39,6 +39,14 @@ module Admin::BaseHelper end end + def collection_to_js(collection, options = {}) + js = collection.collect { |object| object.to_json } + + options_to_js = options.to_json.gsub(/^\{/, '').gsub(/\}$/, '') + + "new Object({ \"collection\": [#{js.join(', ')}], #{options_to_js} })" + end + def growl_message if not flash.empty? %{ diff --git a/app/helpers/admin/custom_fields_helper.rb b/app/helpers/admin/custom_fields_helper.rb index 7bd022ff..b8593deb 100644 --- a/app/helpers/admin/custom_fields_helper.rb +++ b/app/helpers/admin/custom_fields_helper.rb @@ -2,7 +2,7 @@ module Admin::CustomFieldsHelper def options_for_field_kind options = %w{string text category boolean date file}.map do |kind| - [t("admin.custom_fields.kind.#{kind}"), kind] + [t("custom_fields.kind.#{kind}"), kind] end end diff --git a/app/models/asset_collection.rb b/app/models/asset_collection.rb index b8134210..da004d60 100644 --- a/app/models/asset_collection.rb +++ b/app/models/asset_collection.rb @@ -9,7 +9,7 @@ class AssetCollection ## associations ## referenced_in :site - embeds_many :assets + embeds_many :assets, :validate => false ## behaviours ## custom_fields_for :assets diff --git a/app/models/content_instance.rb b/app/models/content_instance.rb index 3dc7d9d5..659c0979 100644 --- a/app/models/content_instance.rb +++ b/app/models/content_instance.rb @@ -43,20 +43,6 @@ class ContentInstance self._visible || self._visible.nil? end - def aliased_attributes # TODO: move it to the custom_fields gem - hash = { :created_at => self.created_at, :updated_at => self.updated_at } - - self.custom_fields.each do |field| - case field.kind - when 'file' then hash[field._alias] = self.send(field._name.to_sym).url - else - hash[field._alias] = self.send(field._name.to_sym) - end - end - - hash - end - def errors_to_hash Hash.new.replace(self.errors) end diff --git a/app/models/content_type.rb b/app/models/content_type.rb index 02e6ab13..fcc41511 100644 --- a/app/models/content_type.rb +++ b/app/models/content_type.rb @@ -14,12 +14,15 @@ class ContentType ## associations ## referenced_in :site - embeds_many :contents, :class_name => 'ContentInstance' do + embeds_many :contents, :class_name => 'ContentInstance', :validate => false do def visible @target.find_all { |c| c.visible? } end end + ## named scopes ## + scope :first_by_slug, lambda { |slug| where(:slug => slug) } + ## indexes ## index [[:site_id, Mongo::ASCENDING], [:slug, Mongo::ASCENDING]] diff --git a/app/views/admin/accounts/new.html.haml b/app/views/admin/accounts/new.html.haml index 8593304d..4148a036 100644 --- a/app/views/admin/accounts/new.html.haml +++ b/app/views/admin/accounts/new.html.haml @@ -8,15 +8,11 @@ = semantic_form_for @account, :url => admin_accounts_url do |f| = f.foldable_inputs :name => :information do - = f.input :name, :required => false + = f.input :name = f.foldable_inputs :name => :credentials do - = f.input :email, :required => false - - = f.custom_input :password, :label => :new_password do - = f.password_field :password - - = f.custom_input :password_confirmation, :label => :new_password_confirmation do - = f.password_field :password_confirmation + = f.input :email + = f.input :password, :input_html => { :autocomplete => "off" } + = f.input :password_confirmation, :input_html => { :autocomplete => "off" } = render 'admin/shared/form_actions', :back_url => edit_admin_current_site_url, :button_label => :create \ No newline at end of file diff --git a/app/views/admin/asset_collections/edit.html.haml b/app/views/admin/asset_collections/edit.html.haml index f386ad3a..abc2ffe7 100644 --- a/app/views/admin/asset_collections/edit.html.haml +++ b/app/views/admin/asset_collections/edit.html.haml @@ -30,4 +30,4 @@ = render 'admin/shared/form_actions', :delete_button => link_to(content_tag(:em, escape_once(' ')) + t('.destroy'), admin_asset_collection_url(@asset_collection), :confirm => t('admin.messages.confirm'), :method => :delete, :class => 'button small remove'), :button_label => :update -= render 'admin/custom_fields/edit_field' \ No newline at end of file += render 'admin/custom_fields/edit_field' diff --git a/app/views/admin/custom_fields/_custom_form.html.haml b/app/views/admin/custom_fields/_custom_form.html.haml index 04f550bc..d60818aa 100644 --- a/app/views/admin/custom_fields/_custom_form.html.haml +++ b/app/views/admin/custom_fields/_custom_form.html.haml @@ -2,7 +2,7 @@ = form.inputs :name => title || :attributes do - form.object.custom_fields.each do |field| - - required = highlighted_field_name == field._name + - 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 diff --git a/app/views/admin/custom_fields/_edit_field.html.haml b/app/views/admin/custom_fields/_edit_field.html.haml index 57274d4c..48cfd42c 100644 --- a/app/views/admin/custom_fields/_edit_field.html.haml +++ b/app/views/admin/custom_fields/_edit_field.html.haml @@ -7,4 +7,9 @@ = g.inputs :name => :attributes do = g.input :_alias = g.input :hint - = g.input :text_formatting, :as => 'select', :collection => options_for_text_formatting, :include_blank => false \ No newline at end of file + = g.input :text_formatting, :as => 'select', :collection => options_for_text_formatting, :include_blank => false, :wrapper_html => { :style => 'display: none' } + + .popup-actions + %p + %button.button.light{ :type => 'submit' } + %span= t('admin.shared.form_actions.update') \ No newline at end of file diff --git a/app/views/admin/custom_fields/_index.html.haml b/app/views/admin/custom_fields/_index.html.haml index 7909c74e..87d49159 100644 --- a/app/views/admin/custom_fields/_index.html.haml +++ b/app/views/admin/custom_fields/_index.html.haml @@ -1,61 +1,49 @@ - collection_name = "#{collection_name.singularize}_custom_fields" - custom_fields = form.object.send(collection_name.to_sym) - ordered_custom_fields = form.object.send(:"ordered_#{collection_name}") +- field_klass = "#{form.object.class.name}#{collection_name.classify}".gsub(/CustomField$/, 'Field').constantize = form.foldable_inputs :name => :custom_fields, :class => 'editable-list fields' do - - ordered_custom_fields.each do |field| - = form.fields_for collection_name.to_sym, field, :child_index => field._index do |g| - %li{ :class => "item added #{'new' if form.object.new_record?} #{'error' unless field.errors.empty?}"} - %span.handle - = image_tag 'admin/form/icons/drag.png' - = g.hidden_field :position, :class => 'position' - - = g.hidden_field :_alias, :class => 'alias' - - = g.hidden_field :hint, :class => 'hint' - - = g.hidden_field :text_formatting, :class => 'text-formatting' - - = g.text_field :label, :class => 'label' - - — - - %em= t("admin.custom_fields.kind.#{field.kind.downcase}") - - = g.select :kind, options_for_field_kind - -   - - %span.actions - = link_to image_tag('admin/form/pen.png'), '#edit-custom-field', :class => 'edit first' - = link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove', :confirm => t('admin.messages.confirm') - - = form.fields_for collection_name.to_sym, custom_fields.build(:label => 'field name', :_alias => ''), :child_index => '-1' do |g| - %li{ :class => 'item template' } + %script{ :type => 'text/x-mustache-template', :name => 'template', :'data-base-input-name' => "#{form.object.class.name.underscore}[#{collection_name}_attributes]" } + %li{ :class => "item {{behaviour_flag}} {{new_record_flag}} {{errors_flag}} {{required_flag}}" } %span.handle = image_tag 'admin/form/icons/drag.png' - = g.hidden_field :position, :class => 'position' + {{#if_existing_record}} + %input{ :name => '{{base_name}}[id]', :value => '{{{id}}}', :type => 'hidden', :'data-field' => 'id' } + %input{ :name => '{{base_name}}[_destroy]', :value => '0', :type => 'hidden', :'data-field' => '_destroy' } + {{/if_existing_record}} - = g.hidden_field :_alias, :class => 'alias' + %input{ :name => '{{base_name}}[position]', :value => '{{{position}}}', :type => 'hidden', :'data-field' => 'position' } - = g.hidden_field :hint, :class => 'hint' + %input{ :name => '{{base_name}}[_alias]', :value => '{{{_alias}}}', :type => 'hidden', :'data-field' => '_alias' } - = g.hidden_field :text_formatting, :class => 'text-formatting' + %input{ :name => '{{base_name}}[hint]', :value => '{{{hint}}}', :type => 'hidden', :'data-field' => 'hint' } - = g.text_field :label, :class => 'string label void' + %input{ :name => '{{base_name}}[text_formatting]', :value => '{{{text_formatting}}}', :type => 'hidden', :'data-field' => 'text_formatting' } + + %input{ :name => '{{base_name}}[label]', :value => '{{{label}}}', :type => 'text', :'data-field' => 'label' } — - %em + %em {{kind_name}} - = g.select :kind, options_for_field_kind + = select_tag '{{base_name}}[kind]', options_for_select(options_for_field_kind), :'data-field' => 'kind'   + %input{ :name => '{{base_name}}[required]', :value => '0', :type => 'hidden', :'data-field' => 'hidden_required' } + %input{ :name => '{{base_name}}[required]', :'{{required_checked}}' => '{{required_checked}}', :value => '1', :type => 'checkbox', :'data-field' => 'required', :id => '{{base_dom_id}}_required' } + + %label{ :for => "{{{base_dom_id}}}_required" }= t('.is_required') + %span.actions = link_to image_tag('admin/form/pen.png'), '#edit-custom-field', :class => 'edit first' = link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove', :confirm => t('admin.messages.confirm') %button{ :class => 'button light add', :type => 'button' } %span!= t('admin.buttons.new_item') + + + %script{ :type => 'text/javascript', :name => 'data' } + != collection_to_js(ordered_custom_fields, :template => field_klass.new(:label => t('.default_label'), :_alias => '', :kind => 'string').to_hash) diff --git a/app/views/admin/my_accounts/edit.html.haml b/app/views/admin/my_accounts/edit.html.haml index 9045864f..5b2e68c0 100644 --- a/app/views/admin/my_accounts/edit.html.haml +++ b/app/views/admin/my_accounts/edit.html.haml @@ -18,10 +18,8 @@ = f.foldable_inputs :name => :credentials do = f.input :email - = f.custom_input :password, :label => :new_password do - = f.password_field :password, :autocomplete => "off" - = f.custom_input :password_confirmation, :label => :new_password_confirmation do - = f.password_field :password_confirmation, :autocomplete => "off" + = f.input :password, :input_html => { :autocomplete => "off" } + = f.input :password_confirmation, :input_html => { :autocomplete => "off" } = f.foldable_inputs :name => :sites, :class => 'sites off' do - @account.sites.each do |site| diff --git a/app/views/admin/shared/_header.html.haml b/app/views/admin/shared/_header.html.haml index ff38a234..a146c2ab 100644 --- a/app/views/admin/shared/_header.html.haml +++ b/app/views/admin/shared/_header.html.haml @@ -6,7 +6,6 @@ - else = link_to current_site.name, admin_pages_url, :class => 'single' - #global-actions-bar != t('.welcome', :name => link_to(current_admin.name, edit_admin_my_account_url)) %span= '|' diff --git a/config/assets.yml b/config/assets.yml index 959163a1..61f5c8dd 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -25,6 +25,7 @@ javascripts: - public/javascripts/admin/application.js custom_fields: - public/javascripts/admin/plugins/fancybox.js + - public/javascripts/admin/plugins/mustache.js - public/javascripts/admin/custom_fields.js edit_custom_fields: - public/javascripts/admin/plugins/tiny_mce/tinymce.js @@ -33,6 +34,7 @@ javascripts: - public/javascripts/admin/custom_fields/category.js asset_collections: - public/javascripts/admin/plugins/fancybox.js + - public/javascripts/admin/plugins/mustache.js - public/javascripts/admin/custom_fields.js - public/javascripts/admin/asset_collections.js assets: diff --git a/config/locales/admin_ui.de.yml b/config/locales/admin_ui.de.yml index c0c250c6..229f5114 100644 --- a/config/locales/admin_ui.de.yml +++ b/config/locales/admin_ui.de.yml @@ -51,13 +51,6 @@ de: custom_fields: edit: title: Benutzerdefinierte Felder bearbeiten - kind: - string: Einfache Texteingabe - text: Text - category: Auswahlbox - boolean: Checkbox - date: Datum - file: Datei text_formatting: none: Keine html: HTML diff --git a/config/locales/admin_ui.en.yml b/config/locales/admin_ui.en.yml index 4c066316..643c06d4 100644 --- a/config/locales/admin_ui.en.yml +++ b/config/locales/admin_ui.en.yml @@ -51,13 +51,6 @@ en: custom_fields: edit: title: Editing custom field - kind: - string: Simple Input - text: Text - category: Select - boolean: Checkbox - date: Date - file: File text_formatting: none: None html: HTML @@ -70,6 +63,9 @@ en: custom_form: edit_categories: Edit options delete_file: Delete file + index: + is_required: is required + default_label: Field name sessions: new: diff --git a/config/locales/admin_ui.fr.yml b/config/locales/admin_ui.fr.yml index bdb4b460..355ec9bf 100644 --- a/config/locales/admin_ui.fr.yml +++ b/config/locales/admin_ui.fr.yml @@ -51,13 +51,6 @@ fr: custom_fields: edit: title: Editer champ personnalisé - kind: - string: Texte - text: Zone de texte - category: Liste déroulante - boolean: Case à cocher - date: Date - file: Fichier text_formatting: none: Aucun html: HTML @@ -70,6 +63,9 @@ fr: custom_form: edit_categories: Editer options delete_file: Supprimer fichier + index: + is_required: est obligatoire + default_label: Nom du champ sessions: new: diff --git a/config/locales/admin_ui.pt-BR.yml b/config/locales/admin_ui.pt-BR.yml index 0bd6732a..ffd81f8a 100644 --- a/config/locales/admin_ui.pt-BR.yml +++ b/config/locales/admin_ui.pt-BR.yml @@ -51,13 +51,6 @@ pt-BR: custom_fields: edit: title: Editando campo customizado - kind: - string: Texto Simples - text: Texto - category: Caixa de Seleção - boolean: Checkbox - date: Data - file: Arquivo text_formatting: none: Nenhum html: HTML diff --git a/config/locales/default.de.yml b/config/locales/default.de.yml index cb8a9b55..931e4694 100644 --- a/config/locales/default.de.yml +++ b/config/locales/default.de.yml @@ -48,7 +48,7 @@ de: other: body: "Inhalte kommen hier rein" - activemodel: + mongoid: attributes: page: title: Titel @@ -166,7 +166,7 @@ de: day: "Tag" month: "Monat" year: "Jahr" - + number: format: precision: 2 diff --git a/config/locales/default.fr.yml b/config/locales/default.fr.yml index c94ed7bd..139f2e47 100644 --- a/config/locales/default.fr.yml +++ b/config/locales/default.fr.yml @@ -46,16 +46,20 @@ fr: title: "Page non trouvée" body: "Contenu de la page d'erreur 404" other: - body: "Le contenu va ici" + body: "{% extends 'parent' %}" - activemodel: + mongoid: attributes: page: title: Titre - parent: Parent + parent: Dossier parent + parent_id: Dossier parent slug: Raccourci + listed: Menu templatized: Templatisée published: Publiée + redirect: Redirection + redirect_url: Url de redirection cache_strategy: Cache content_type: name: Nom @@ -72,11 +76,11 @@ fr: name: Nom source: Fichier account: - email: Email + email: E-mail name: Nom language: Langue - new_password: "Nouveau mot de passe" - new_password_confirmation: "Confirmation nouveau mot de passe" + password: "Nouveau mot de passe" + password_confirmation: "Confirmation nouveau mot de passe" snippet: body: Code slug: Raccourci @@ -90,6 +94,12 @@ fr: restricted_access: Activer ? access_login: Identifiant access_password: "Mot de passe" + custom_fields: + field: + _alias: Alias + hint: Aide + required: Requis ? + text_formatting: Formattage pagination: previous: "« Précédent" diff --git a/config/locales/default.pt-BR.yml b/config/locales/default.pt-BR.yml index 2882a12e..1a61ef7a 100644 --- a/config/locales/default.pt-BR.yml +++ b/config/locales/default.pt-BR.yml @@ -2,7 +2,7 @@ pt-BR: errors: # The default format use in full error messages. format: "%{attribute} %{message}" - + messages: inclusion: "não está incluído na lista" exclusion: "não está disponível" @@ -23,7 +23,7 @@ pt-BR: less_than_or_equal_to: "deve ser menor ou igual a %{count}" odd: "deve ser ímpar" even: "deve ser par" - + domain_taken: "%{value} já está em uso." invalid_domain: "%{value} é inválido." needs_admin_account: "Uma conta de administrador é necessário pelo menos." @@ -33,8 +33,8 @@ pt-BR: liquid_syntax: "Erro de sintaxe do Liquid, por favor verifique a sintaxe" liquid_extend: "A página extende um template que não existe." invalid_theme_file: "não pode ser vazio ou não é um arquivo zip" - - + + date: formats: default: "%d/%m/%Y" @@ -51,8 +51,8 @@ pt-BR: body: "Conteúdo da página de erro 404" other: body: "Conteúdo vai aqui" - - activemodel: + + mongoid: attributes: page: title: Título @@ -94,7 +94,7 @@ pt-BR: restricted_access: Restrito ? access_login: Login access_password: "senha" - + pagination: previous: "« Anterior" next: "Próximo »" @@ -106,15 +106,15 @@ pt-BR: long: "%e %B %Y" long_ordinal: "%e %B %Y" only_day: "%e" - + day_names: [Domingo, Segunda, Terça, Quarta, Quinta, Sexta, Sábado] abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sáb] - + month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro] abbr_month_names: [~, Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez] order: [ :day, :month, :year ] - - + + time: formats: default: "%d %B %Y %H:%M" @@ -125,54 +125,54 @@ pt-BR: only_second: "%S" am: 'am' pm: 'pm' - + datetime: distance_in_words: half_a_minute: 'meio minuto' less_than_x_seconds: one: 'menos de 1 segundo' other: 'menos de %{count} segundos' - + x_seconds: one: '1 segundo' other: '%{count} segundos' - + less_than_x_minutes: one: 'menos de um minuto' other: 'menos de %{count} minutos' - + x_minutes: one: '1 minuto' other: '%{count} minutos' - + about_x_hours: one: 'aproximadamente 1 hora' other: 'aproximadamente %{count} horas' - + x_days: one: '1 dia' other: '%{count} dias' - + about_x_months: one: 'aproximadamente 1 mês' other: 'aproximadamente %{count} meses' - + x_months: one: '1 mês' other: '%{count} meses' - + about_x_years: one: 'aproximadamente 1 ano' other: 'aproximadamente %{count} anos' - + over_x_years: one: 'mais de 1 ano' other: 'mais de %{count} anos' - + almost_x_years: one: 'quase 1 ano' other: 'quase %{count} anos' - + prompts: year: "Ano" month: "Mês" @@ -180,8 +180,8 @@ pt-BR: hour: "Hora" minute: "Minuto" second: "Segundos" - - + + number: format: precision: 3 @@ -203,7 +203,7 @@ pt-BR: mb: 'Mb' gb: 'Gb' tb: 'Tb' - + support: array: sentence_connector: ' e ' @@ -211,4 +211,3 @@ pt-BR: words_connector: ", " two_words_connector: " e " last_word_connector: " e " - \ No newline at end of file diff --git a/config/locales/formtastic.fr.yml b/config/locales/formtastic.fr.yml index 92fe9ca4..872083d7 100644 --- a/config/locales/formtastic.fr.yml +++ b/config/locales/formtastic.fr.yml @@ -41,16 +41,6 @@ fr: reset: Remettre à zéro content_type: api_accounts: Comptes à notifier - page: - title: Titre - parent_id: Dossier - slug: Raccourci URL - published: Publiée - templatized: Templatisée - listed: Menu - redirect: Redirection - redirect_url: Url de redirection - cache_strategy: Cache hints: page: @@ -78,5 +68,6 @@ fr: samples: "Si activé, les contenus et les média seront aussi copiés lors de l'import" reset: "Si activé, toutes les données de votre site seront détruites avant l'import du nouveau site" content_type: + slug: Nom utilisé dans les templates liquid afin d'accéder aux enregistrements de ce modèle api_enabled: "Utilisé pour autoriser la création de nouvelles instances de l'extérieur (ex.: les messages dans un formulaire de contact)" api_accounts: "Un email de notification sera envoyé à chaque compte listé ci-dessus lors de la création d'une nouvelle instance" diff --git a/doc/TODO b/doc/TODO index 27969795..68100c61 100644 --- a/doc/TODO +++ b/doc/TODO @@ -5,10 +5,11 @@ x pull requests #31 et #32 - bugs - editable_elements slug becomes nil - uploading videos http://groups.google.com/group/carrierwave/browse_thread/thread/6e211d98f1ff4bc0/51717c2167695ca2?lnk=gst&q=version#51717c2167695ca2 - - editable_elements not updated (doesn't use index anymore) - - custom_fields not deleted (doesn't use index anymore) + x editable_elements not updated (doesn't use index anymore) + x custom_fields not deleted (doesn't use index anymore) - editable_elements: inheritable: false (Mattias) - 2 different sites on the same main domain (one in www, the other one in something else) (Raphael Costa) +- password / new_password BACKLOG: diff --git a/lib/locomotive.rb b/lib/locomotive.rb index 830edcc5..d0a782a7 100644 --- a/lib/locomotive.rb +++ b/lib/locomotive.rb @@ -56,6 +56,10 @@ module Locomotive # Devise Devise.mailer_sender = self.config.mailer_sender + + # Load all the dynamic classes (custom fields) + ContentType.all.collect(&:fetch_content_klass) + AssetCollection.all.collect(&:fetch_asset_klass) end def self.logger(message) diff --git a/public/images/admin/form/pen.png b/public/images/admin/form/pen.png index f8d4f8d2..8afada02 100644 Binary files a/public/images/admin/form/pen.png and b/public/images/admin/form/pen.png differ diff --git a/public/javascripts/admin/content_types.js b/public/javascripts/admin/content_types.js index 86d1919f..2d8f2564 100644 --- a/public/javascripts/admin/content_types.js +++ b/public/javascripts/admin/content_types.js @@ -16,15 +16,15 @@ $(document).ready(function() { // api enabled ? - console.log('subscribing...'); + // console.log('subscribing...'); $.subscribe('toggle.content_type_api_enabled.checked', function(event, data) { - console.log('checked'); + // console.log('checked'); $('#content_type_api_accounts_input').show(); }, []); $.subscribe('toggle.content_type_api_enabled.unchecked', function(event, data) { - console.log('unchecked'); + // console.log('unchecked'); $('#content_type_api_accounts_input').hide(); }, []); diff --git a/public/javascripts/admin/custom_fields.js b/public/javascripts/admin/custom_fields.js index 5163d29a..91047b98 100644 --- a/public/javascripts/admin/custom_fields.js +++ b/public/javascripts/admin/custom_fields.js @@ -1,155 +1,360 @@ $(document).ready(function() { - $('fieldset.fields').parents('form').submit(function() { - $('fieldset.fields li.template input, fieldset.fields li.template select').attr('disabled', 'disabled'); - }); + var wrapper = $('fieldset.editable-list.fields'); + var list = wrapper.find('ol'); + var template = wrapper.find('script[name=template]').html(); + var baseInputName = wrapper.find('script[name=template]').attr('data-base-input-name'); + var data = eval(wrapper.find('script[name=data]').html()); + var index = 0; + + var domFieldVal = function(domField, fieldName, val) { + var el = domField.find('input[data-field=' + fieldName + '], select[data-field=' + fieldName + ']'); + return (typeof(val) == 'undefined' ? el.val() : el.val(val)); + } + + var domBoxAttr = function(fieldName) { + return $('#fancybox-wrap form').find('input[name=custom_fields_field[' + fieldName + ']], select[name=custom_fields_field[' + fieldName + ']]'); + } + + var domBoxAttrVal = function(fieldName, val) { + return (typeof(val) == 'undefined' ? domBoxAttr(fieldName).val() : domBoxAttr(fieldName).val(val)); + } + + /* ___ Register all the different events when a field is added (destroy, edit details, ...etc) ___ */ + + var registerFieldEvents = function(field, domField) { + // select + domField.find('em').click(function() { + $(this).hide(); + $(this).next().show(); + }); + + domField.find('select').each(function() { + var select = $(this); + select.hover(function() { + clearTimeout($.data(select, 'timer')); + }, + function() { + $.data(select, 'timer', setTimeout(function() { + select.hide(); + select.prev().show(); + }, 1000)); + }).change(function() { + selectOnChange(select); + }); + }); + + // checkbox + domField.find('input[type=checkbox]').click(function() { domField.toggleClass('required'); }); + + // edit + domField.find('a.edit').click(function(e) { + var link = $(this); + var attributes = ['_alias', 'hint', 'text_formatting']; + + $.fancybox({ + titleShow: false, + content: $(link.attr('href')).parent().html(), + padding: 0, + onComplete: function() { + $('#fancybox-wrap .actions button[type=submit]').click(function(e) { + $.each(attributes, function(index, name) { + var val = domBoxAttrVal(name).trim(); + if (val != '') domFieldVal(domField, name, val); + }); + domBoxAttr('text_formatting').parent().hide(); + + $.fancybox.close(); + e.preventDefault(); e.stopPropagation(); + }); + + // copy current val to the form in the box + $.each(attributes, function(index, name) { + var val = domFieldVal(domField, name).trim(); + if (val == '' && name == '_alias') val = makeSlug(domFieldVal(domField, 'label')); + + domBoxAttrVal(name, val); + }); + if (domFieldVal(domField, 'kind').toLowerCase() == 'text') domBoxAttr('text_formatting').parents('li').show(); + } + }); + e.preventDefault(); e.stopPropagation(); + }); + + // remove + domField.find('a.remove').click(function(e) { + if (confirm($(this).attr('data-confirm'))) { + if (field.new_record) + domField.remove(); + else + domField.hide().find('input[data-field=_destroy]').val(1); + refreshPosition(); + } + e.preventDefault(); e.stopPropagation(); + }); + } + + var registerFieldTemplateEvents = function(domField) { + // checkbox + domField.find('input[type=checkbox]').click(function() { domField.toggleClass('required'); }); + + var labelDom = domField.find('input[data-field=label]').focus(function() { + if ($(this).val() == data.template.label) $(this).val(''); + }).focusout(function() { + if ($(this).val() == '') $(this).val(data.template.label); + }); + var kindDom = domField.find('select[data-field=kind]'); + var requiredDom = domField.find('input[data-field=required]'); + + // bind the "Add field" button + domField.find('button').click(function(e) { + var newField = $.extend({}, data.template); + newField._alias = ''; + newField.label = labelDom.val().trim(); + newField.kind = kindDom.val(); + newField.required = requiredDom.is(':checked'); + + if (newField.label == '' || newField.label == data.template.label) return false; + + // reset template values + labelDom.val(data.template.label); + kindDom.val(data.template.kind); + requiredDom.attr('checked', ''); + domField.removeClass('required'); + + addField(newField, { refreshPosition: true }); + + e.preventDefault(); e.stopPropagation(); + }); + } + + var refreshPosition = function() { + $.each(list.find('li.added:visible input[data-field=position]'), function(index) { $(this).val(index); }); + } - var defaultValue = $('fieldset.fields li.template input[type=text]').val(); var selectOnChange = function(select) { - select.hide(); - select.prev() + select.hide().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); - }); - } + /* ___ Add a field in the list of fields ___ */ + var addField = function(field, options) { + options = $.extend({ + 'is_template': false, + 'refreshPosition': false + }, options); - /* __ 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); }); + field = $.extend({ + behaviour_flag: function() { return options.is_template ? 'template' : 'added' }, + new_record_flag: function() { return this.new_record == true && options.is_template == false ? 'new' : '' }, + errors_flag: function() { return this.errors.length > 0 ? 'error' : '' }, + required_flag: function() { return this.required ? 'required' : ''; }, + base_name: function() { return options.is_template ? '' : baseInputName + "[" + index + "]"; }, + base_dom_id: function() { return options.is_template ? 'custom_field_template' : 'custom_field_' + index; }, + required_checked: function() { return this.required ? 'checked' : ''; }, + if_existing_record: function() { return this.new_record == false } + }, field); - select.prev().click(function() { - $(this).hide(); - select.show(); - }); - }); + var html = Mustache.to_html(template, field); - $('fieldset.fields li.template input[type=text]').focus(function() { - if ($(this).hasClass('void') && $(this).parents('li').hasClass('template')) - $(this).val('').removeClass('void'); - }); + var domField = null; - $('fieldset.fields li.template button').click(function() { - var lastRow = $(this).parents('li.template'); + if (options.is_template) { + domField = list.append(html).find('.template'); - var label = lastRow.find('input.label').val().trim(); - if (label == '' || label == defaultValue) return false; + registerFieldTemplateEvents(domField); + } + else { + domField = list.find('> .template').before(html).prev('li'); - var newRow = lastRow.clone(true).removeClass('template').addClass('added new').insertBefore(lastRow); + registerFieldEvents(field, domField); - var dateFragment = '[' + new Date().getTime() + ']'; - newRow.find('input, select').each(function(index) { - $(this).attr('name', $(this).attr('name').replace('[-1]', dateFragment)); - }); + list.sortable('refresh'); - 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(); - }); + if (options.refreshPosition) refreshPosition(); - // 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(); + index++; } - e.preventDefault(); - e.stopPropagation(); - }); + domField.find('select').val(field.kind); + domField.find('em').html(domField.find('select option:selected').text()); + } - // sortable list - $("fieldset.fields ol").sortable({ - handle: 'span.handle', - items: 'li:not(.template)', - axis: 'y', - update: refreshPosition - }); + /* ___ SETUP ___ */ + var setup = function() { + // sortable list + list.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(); - }); + // add the template field used to insert the new ones + addField(data.template, { is_template: true }); - var parent = link.parent(); + // add the existing fields (if present) + for (var i = 0; i < data.collection.length; i++) { + addField(data.collection[i]); + } + } - 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'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); +// } +// }) +// }); +// }); diff --git a/public/javascripts/admin/plugins/mustache.js b/public/javascripts/admin/plugins/mustache.js new file mode 100644 index 00000000..c41eb6a6 --- /dev/null +++ b/public/javascripts/admin/plugins/mustache.js @@ -0,0 +1,331 @@ +/* + mustache.js — Logic-less templates in JavaScript + + See http://mustache.github.com/ for more info. +*/ + +var Mustache = function() { + var Renderer = function() {}; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_implemented: { + "IMPLICIT-ITERATOR": true + }, + context: {}, + + render: function(template, context, partials, in_recursion) { + // reset buffer & set context + if(!in_recursion) { + this.context = context; + this.buffer = []; // TODO: make this non-lazy + } + + // fail fast + if(!this.includes("", template)) { + if(in_recursion) { + return template; + } else { + this.send(template); + return; + } + } + + template = this.render_pragmas(template); + var html = this.render_section(template, context, partials); + if(in_recursion) { + return this.render_tags(html, context, partials, in_recursion); + } + + this.render_tags(html, context, partials, in_recursion); + }, + + /* + Sends parsed lines + */ + send: function(line) { + if(line != "") { + this.buffer.push(line); + } + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + // no pragmas + if(!this.includes("%", template)) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + + this.ctag); + return template.replace(regex, function(match, pragma, options) { + if(!that.pragmas_implemented[pragma]) { + throw({message: + "This implementation of mustache doesn't understand the '" + + pragma + "' pragma"}); + } + that.pragmas[pragma] = {}; + if(options) { + var opts = options.split("="); + that.pragmas[pragma][opts[0]] = opts[1]; + } + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the curent scope and render it + */ + render_partial: function(name, context, partials) { + name = this.trim(name); + if(!partials || partials[name] === undefined) { + throw({message: "unknown_partial '" + name + "'"}); + } + if(typeof(context[name]) != "object") { + return this.render(partials[name], context, partials, true); + } + return this.render(partials[name], context[name], partials, true); + }, + + /* + Renders inverted (^) and normal (#) sections + */ + render_section: function(template, context, partials) { + if(!this.includes("#", template) && !this.includes("^", template)) { + return template; + } + + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, type, name, content) { + var value = that.find(name, context); + if(type == "^") { // inverted section + if(!value || that.is_array(value) && value.length === 0) { + // false or empty list, render it + return that.render(content, context, partials, true); + } else { + return ""; + } + } else if(type == "#") { // normal section + if(that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, function(row) { + return that.render(content, that.create_context(row), + partials, true); + }).join(""); + } else if(that.is_object(value)) { // Object, Use it as subcontext! + return that.render(content, that.create_context(value), + partials, true); + } else if(typeof value === "function") { + // higher order section + return value.call(context, content, function(text) { + return that.render(text, context, partials, true); + }); + } else if(value) { // boolean section + return that.render(content, context, partials, true); + } else { + return ""; + } + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + + that.ctag + "+", "g"); + }; + + var regex = new_regex(); + var tag_replace_callback = function(match, operator, name) { + switch(operator) { + case "!": // ignore comments + return ""; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }; + var lines = template.split("\n"); + for(var i = 0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, tag_replace_callback, this); + if(!in_recursion) { + this.send(lines[i]); + } + } + + if(in_recursion) { + return lines.join("\n"); + } + }, + + set_delimiters: function(delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, + + escape_regex: function(text) { + // thank you Simon Willison + if(!arguments.callee.sRE) { + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + arguments.callee.sRE = new RegExp( + '(\\' + specials.join('|\\') + ')', 'g' + ); + } + return text.replace(arguments.callee.sRE, '\\$1'); + }, + + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function(name, context) { + name = this.trim(name); + + // Checks whether a value is thruthy or false or 0 + function is_kinda_truthy(bool) { + return bool === false || bool === 0 || bool; + } + + var value = context; + var path = name.split(/\./); + for(var i = 0; i < path.length; i++) { + name = path[i]; + if(value && is_kinda_truthy(value[name])) { + value = value[name]; + } else if(i == 0 && is_kinda_truthy(this.context[name])) { + value = this.context[name]; + } else { + value = undefined; + } + } + + if(typeof value === "function") { + return value.apply(context); + } + if(value !== undefined) { + return value; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* includes tag */ + includes: function(needle, haystack) { + return haystack.indexOf(this.otag + needle) != -1; + }, + + /* + Does away with nasty characters + */ + escape: function(s) { + s = String(s === null ? "" : s); + return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '"'; + case "'": return '''; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if(this.is_object(_context)) { + return _context; + } else { + var iterator = "."; + if(this.pragmas["IMPLICIT-ITERATOR"]) { + iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; + } + var ctx = {}; + ctx[iterator] = _context; + return ctx; + } + }, + + is_object: function(a) { + return a && typeof a == "object"; + }, + + is_array: function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }, + + /* + Gets rid of leading and trailing whitespace + */ + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ""); + }, + + /* + Why, why, why? Because IE. Cry, cry cry. + */ + map: function(array, fn) { + if (typeof array.map == "function") { + return array.map(fn); + } else { + var r = []; + var l = array.length; + for(var i = 0; i < l; i++) { + r.push(fn(array[i])); + } + return r; + } + } + }; + + return({ + name: "mustache.js", + version: "0.3.1-dev", + + /* + Turns a template and view into HTML + */ + to_html: function(template, view, partials, send_fun) { + var renderer = new Renderer(); + if(send_fun) { + renderer.send = send_fun; + } + renderer.render(template, view, partials); + if(!send_fun) { + return renderer.buffer.join("\n"); + } + } + }); +}(); \ No newline at end of file diff --git a/public/stylesheets/admin/fancybox_changes.css b/public/stylesheets/admin/fancybox_changes.css index 3c91839a..71457d3e 100644 --- a/public/stylesheets/admin/fancybox_changes.css +++ b/public/stylesheets/admin/fancybox_changes.css @@ -45,7 +45,7 @@ background-image: url("/images/admin/form/big_item-popup.png"); } -#fancybox-inner p { color:#8B8D9A; font-size:0.8em; } +#fancybox-inner p { color: #8B8D9A; font-size: 0.8em; } /* ___ asset picker ___ */ @@ -63,6 +63,7 @@ div.asset-picker ul li .more { top: 8px; } /* ___ custom fields ___ */ #edit-custom-field { + padding: 10px; width: 473px; } diff --git a/public/stylesheets/admin/formtastic_changes.css b/public/stylesheets/admin/formtastic_changes.css index d1dc8b1c..e5816fbf 100644 --- a/public/stylesheets/admin/formtastic_changes.css +++ b/public/stylesheets/admin/formtastic_changes.css @@ -266,12 +266,18 @@ form.formtastic fieldset.editable-list ol li.added select { } form.formtastic fieldset.editable-list ol li.added em { + display: inline-block; + position: relative; + top: -1px; color: #8b8d9a; font-size: 0.9em; font-style: italic; margin-left: 3px; + border: 1px solid transparent; + padding: 2px 5px; + height: 18px; + line-height: 16px; } -form.formtastic fieldset.editable-list ol li.added em { border: 1px solid transparent; padding: 2px 5px; } form.formtastic fieldset.editable-list ol li.added em:hover { background: #fffbe5; border: 1px dotted #efe4a5; @@ -280,6 +286,11 @@ form.formtastic fieldset.editable-list ol li.added em:hover { font-weight: bold; } +form.formtastic fieldset.editable-list ol li.added select, +form.formtastic fieldset.editable-list ol li.added em { + width: 120px; +} + form.formtastic fieldset.editable-list ol li.added input { position: relative; top: -1px; @@ -325,7 +336,11 @@ form.formtastic fieldset.editable-list ol li.template { padding-top: 10px; } -form.formtastic fieldset.editable-list ol li.template input { +form.formtastic fieldset.editable-list ol li.template em { + display: none; +} + +form.formtastic fieldset.editable-list ol li.template input[type=text] { display: inline; margin-left: 10px; padding: 4px; @@ -340,6 +355,7 @@ form.formtastic fieldset.editable-list ol li.template input { form.formtastic fieldset.editable-list ol li.template select { display: inline; + top: -1px; } form.formtastic fieldset.editable-list ol li.template span.handle { @@ -367,6 +383,25 @@ form.formtastic fieldset.editable-list ol li.template span.actions button span { font-size: 0.8em; } +/* ___ custom fields: inherits from editable-list ___ */ + +form.formtastic fieldset.fields input[type=checkbox] { + margin: 0 0 0 20px; + width: 20px; +} + +form.formtastic fieldset.fields label { + display: inline; + float: none; + padding: 0px; + font-weight: normal; + color: #8B8D9A; +} + +form.formtastic fieldset.fields li.required label { + font-weight: bold; +} + /* ___ editable-list (content type validations) ___ */ form.formtastic fieldset.validations ol li.added em.key {