diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index ba97469f..e12c7cf1 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -12,6 +12,18 @@ class Admin::BaseController < ::ApplicationController protected + def flash_success! + flash[:success] = translate_flash_msg(:successful) + end + + def flash_error! + flash[:error] = translate_flash_msg(:failed) + end + + def translate_flash_msg(kind) + t("#{kind.to_s}_#{action_name}", :scope => [:admin, controller_name.underscore.gsub('/', '.'), :messages]) + end + def self.sections(main, sub = nil) write_inheritable_attribute(:sections, { :main => main, :sub => sub }) end diff --git a/app/controllers/admin/pages_controller.rb b/app/controllers/admin/pages_controller.rb index 7e2cc315..33ccc4e6 100644 --- a/app/controllers/admin/pages_controller.rb +++ b/app/controllers/admin/pages_controller.rb @@ -3,6 +3,52 @@ class Admin::PagesController < Admin::BaseController sections 'contents' def index + @pages = Page.all + end + + def new + @page = current_site.pages.build + end + + def edit + @page = current_site.pages.find(params[:id]) + end + + def create + @page = current_site.pages.build(params[:page]) + + if @page.save + flash_success! + redirect_to edit_admin_page_url(@page) + else + flash_error! + render :action => 'new' + end + end + + def update + @page = current_site.pages.find(params[:id]) + + if @page.update_attributes(params[:page]) + flash_success! + redirect_to edit_admin_page_url(@page) + else + flash_error! + render :action => "edit" + end + end + + def destroy + @page = current_site.pages.find(params[:id]) + + begin + @page.destroy + flash_success! + rescue Exception => e + flash[:error] = e.to_s + end + + redirect_to admin_pages_url end end \ No newline at end of file diff --git a/app/helpers/admin/base_helper.rb b/app/helpers/admin/base_helper.rb index 6f637f44..c5fcc1f6 100644 --- a/app/helpers/admin/base_helper.rb +++ b/app/helpers/admin/base_helper.rb @@ -1,8 +1,15 @@ module Admin::BaseHelper - + def admin_menu_item(name, url) label = content_tag(:em) + escape_once(' ') + t("admin.shared.menu.#{name}") content_tag(:li, link_to(label, url), :class => name.dasherize) end + def admin_button_tag(text, url, options = {}) + text = text.is_a?(Symbol) ? t(".#{text}") : text + link_to(url, options) do + content_tag(:span, text) + end + end + end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 108c796c..ee38f940 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,6 +19,16 @@ module ApplicationHelper end end + def growl_message + if not flash.empty? + %{ + $(document).ready(function() { + $.growl("#{flash.keys.first}", "#{flash.values.first}"); + }); + }.to_s + end + end + def nocoffee_tag link_to content_tag(:em, 'no') + 'Coffee', 'http://www.nocoffee.fr', :id => 'nocoffee' end diff --git a/app/models/page.rb b/app/models/page.rb new file mode 100644 index 00000000..73d6ae6e --- /dev/null +++ b/app/models/page.rb @@ -0,0 +1,37 @@ +class Page + include Mongoid::Document + include Mongoid::Timestamps + + ## fields ## + field :title + field :path + field :published, :type => Boolean, :default => false + field :keywords + field :description + + ## associations ## + belongs_to_related :site + embeds_many :parts, :class_name => 'PagePart' + + ## validations ## + validates_presence_of :site, :title, :path + validates_uniqueness_of :path, :scope => :site_id + validate :path_must_not_begin_with_reserverd_keywords + + ## callbacks ## + before_create :add_body_part + + ## named scopes ## + + ## methods ## + + def add_body_part + self.parts.build :name => 'body', :value => '---body here---' + end + + def path_must_not_begin_with_reserverd_keywords + if (self.path =~ /^(#{Locomotive.config.forbidden_paths.join('|')})\//) == 0 + errors.add(:path, :reserved_keywords) + end + end +end \ No newline at end of file diff --git a/app/models/page_part.rb b/app/models/page_part.rb new file mode 100644 index 00000000..2d70c2dc --- /dev/null +++ b/app/models/page_part.rb @@ -0,0 +1,20 @@ +class PagePart + include Mongoid::Document + include Mongoid::Timestamps + + ## fields ## + field :name, :type => String + field :slug, :type => String + field :value, :type => String + field :disabled, :type => Boolean, :default => false + + ## associations ## + embedded_in :page, :inverse_of => :parts + + ## validations ## + validates_presence_of :name, :slug + + ## callbacks ## + before_validate { |p| p.slug ||= p.name.slugify if p.name.present? } + +end \ No newline at end of file diff --git a/app/models/site.rb b/app/models/site.rb index 0419113d..4dac0347 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -8,6 +8,9 @@ class Site field :domains, :type => Array, :default => [] field :account_ids, :type => Array, :default => [] + ## associations ## + has_many_related :pages + ## validations ## validates_presence_of :name, :subdomain validates_uniqueness_of :subdomain diff --git a/app/views/admin/pages/_form.html.haml b/app/views/admin/pages/_form.html.haml index e69de29b..7528ab8b 100644 --- a/app/views/admin/pages/_form.html.haml +++ b/app/views/admin/pages/_form.html.haml @@ -0,0 +1,69 @@ += f.foldable_inputs :name => :information do + + = f.input :title + + = f.custom_input :path, :css => 'path' do + & /#{f.text_field :path}.html + + = f.custom_input :published, :css => 'toggle' do + = f.check_box :published + += f.foldable_inputs :name => :meta do + + = f.input :keywords + = f.input :description + + + + +/ <% f.foldable_inputs :name => :information do %> +/ <%= f.input :title, :required => false %> +/ +/ <% f.custom_input :path, :css => 'path' do %> +/ /<%= f.text_field :path %>.html +/ <% end %> +/ +/ <% f.custom_input :layout do %> +/ <%= f.select :layout_id, current_site.layouts.all.collect { |l| [l.name, l.id] }, :include_blank => true %> +/ <% end %> +/ +/ <%= f.input :keywords, :required => false %> +/ <%= f.input :description, :required => false %> +/ +/ <% f.custom_input :cache_expires_in do %> +/ <%= f.select :cache_expires_in, options_for_page_cache_expiration %> +/ <% end %> +/ +/ <% f.custom_input :visible, :css => 'checkbox' do %> +/ <%= f.check_box :visible %> +/ <% end %> +/ <% end %> +/ +/ <% unless f.object.new_record? %> +/
+/
+/ <% if body_part? %> +/ <%= t('admin.pages.form.body') %><% end -%><% @page.parts.active.each_with_index do |p, index| -%><%= p.slug.humanize %> +/ <% end %> +/
+/ +/
+/ +/
+/
+/ +/ <% content_for :head do %> +/ <%= javascript_include_tag 'admin/plugins/iphoneSwitch', 'admin/plugins/checkbox', 'admin/plugins/carousel', 'codemirror/codemirror', 'admin/pages' %> +/ <%= stylesheet_link_tag 'carousel', 'admin/page_parts' %> +/ <% end %> +/ <% end %> \ No newline at end of file diff --git a/app/views/admin/pages/edit.html.haml b/app/views/admin/pages/edit.html.haml index e69de29b..5fa1cc8c 100644 --- a/app/views/admin/pages/edit.html.haml +++ b/app/views/admin/pages/edit.html.haml @@ -0,0 +1,52 @@ +- title link_to(@page.title.blank? ? @page.title_was : @page.title, '#', :rel => 'page_title', :title => t('.ask_for_title'), :class => 'editable') + +- content_for :submenu do + = render 'admin/shared/contents_menu' + +- content_for :buttons do + = admin_button_tag :show, '#', :class => 'show' + +%p= t('.help') + += semantic_form_for @page, :url => admin_page_url(@page), :html => { :class => 'shortcut-enabled' } do |form| + + = render 'form', :f => form + + = render 'admin/shared/form_actions', :back_url => admin_pages_url, :button_label => :update + + +/ <% content_for :title do %> +/ <%= link_to @page.title.blank? ? @page.title_was : @page.title, '#', :class => 'editable' %> +/ <% end %> +/ +/ <% content_for :buttons do %> +/ <% show_page_button(@page) %> +/ <% end %> +/ +/

<%= t('admin.pages.edit.help') %>

+/ +/ <% semantic_form_for @page, :url => admin_page_url(@page), :html => { :class => 'save-shortcut' } do |form| %> +/ +/ <%= render :partial => 'form', :locals => { :f => form } %> +/ +/
+/
+/

+/ <%= link_to '← ' + t('admin.common.buttons.back'), admin_pages_url %> +/

+/
+/ +/
+/

+/ +/

+/
+/
+/
+/ <% end %> +/ +/ <% content_for :submenu do %> +/ <%= render :partial => '/shared/admin/contents_menu' %> +/ <% end %> \ No newline at end of file diff --git a/app/views/admin/pages/index.html.haml b/app/views/admin/pages/index.html.haml index 7bbc51ba..14ab6cb2 100644 --- a/app/views/admin/pages/index.html.haml +++ b/app/views/admin/pages/index.html.haml @@ -1,5 +1,17 @@ - title t('.title') - - content_for :submenu do = render 'admin/shared/contents_menu' + +- content_for :buttons do + = admin_button_tag :new, new_admin_page_url, :class => 'add' + +%p= t('.help') + +- if @pages.empty? + %p.no-items= t('.no_items', :url => new_admin_page_url) +- else + %ul#pages-list + - @pages.each do |page| + %li + = link_to page.title, edit_admin_page_url(page) \ No newline at end of file diff --git a/app/views/admin/pages/new.html.haml b/app/views/admin/pages/new.html.haml index e69de29b..27d1d765 100644 --- a/app/views/admin/pages/new.html.haml +++ b/app/views/admin/pages/new.html.haml @@ -0,0 +1,12 @@ +- title t('.title') + +- content_for :submenu do + = render 'admin/shared/contents_menu' + +%p= t('.help') + += semantic_form_for @page, :url => admin_pages_url do |form| + + = render 'form', :f => form + + = render 'admin/shared/form_actions', :back_url => admin_pages_url, :button_label => :create \ No newline at end of file diff --git a/app/views/admin/shared/_form_actions.html.haml b/app/views/admin/shared/_form_actions.html.haml new file mode 100644 index 00000000..f86f09b3 --- /dev/null +++ b/app/views/admin/shared/_form_actions.html.haml @@ -0,0 +1,11 @@ +.actions + .span-12 + %p + = link_to escape_once('← ') + t('.back'), back_url + + .span-12.last + %p + %button.button.light{ :type => 'submit' } + %span= button_label.is_a?(Symbol) ? t(".#{button_label}") : button_label + + .clear \ No newline at end of file diff --git a/app/views/admin/shared/_head.html.haml b/app/views/admin/shared/_head.html.haml index 40ee6eaf..9bc8da74 100644 --- a/app/views/admin/shared/_head.html.haml +++ b/app/views/admin/shared/_head.html.haml @@ -2,12 +2,17 @@ = csrf_meta_tag +%meta{ :name => 'locale', :content => I18n.locale } + = stylesheet_link_tag 'blueprint/screen', :media => 'screen' / [if IE] = stylesheet_link_tag('blueprint/ie', :media => 'screen') -= stylesheet_link_tag 'admin/layout', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production? += stylesheet_link_tag 'admin/layout', 'admin/plugins/toggle', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production? -= javascript_include_tag 'jquery', 'jquery.ui', 'rails', :cache => Rails.env.production? += javascript_include_tag 'jquery', 'jquery.ui', 'rails', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/application', :cache => Rails.env.production? + +%script{ :type => 'text/javascript' } + = find_and_preserve(growl_message) = yield :head \ No newline at end of file diff --git a/config/initializers/formtastic.rb b/config/initializers/formtastic.rb index 7c1fa651..0f7a24c4 100644 --- a/config/initializers/formtastic.rb +++ b/config/initializers/formtastic.rb @@ -1 +1,4 @@ -Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true \ No newline at end of file +Formtastic::SemanticFormHelper.builder = MiscFormBuilder + +Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true + diff --git a/config/initializers/locomotive.rb b/config/initializers/locomotive.rb index ff12be7b..5e4503f4 100644 --- a/config/initializers/locomotive.rb +++ b/config/initializers/locomotive.rb @@ -1,4 +1,5 @@ require 'lib/locomotive.rb' +require 'lib/core_ext.rb' Locomotive.configure do |config| config.default_domain = 'example.com' diff --git a/config/initializers/mongoid.rb b/config/initializers/mongoid.rb index 0e53d53f..7e77f27b 100644 --- a/config/initializers/mongoid.rb +++ b/config/initializers/mongoid.rb @@ -11,3 +11,19 @@ Mongoid.configure do |config| # Mongo::Connection.new(host, @settings["slave_two"]["port"], :slave_ok => true).db(name) # ] end + +## various patches + +# Enabling scope in validates_uniqueness_of validation +module Mongoid #:nodoc: + module Validations #:nodoc: + class UniquenessValidator < ActiveModel::EachValidator + def validate_each(document, attribute, value, scope = nil) + criteria = { attribute => value, :_id.ne => document._id } + criteria[scope] = document.send(scope) if scope + return if document.class.where(criteria).empty? + document.errors.add(attribute, :taken, :default => options[:message], :value => value) + end + end + end +end diff --git a/config/locales/admin_ui_en.yml b/config/locales/admin_ui_en.yml index ff11cdce..05f86af9 100644 --- a/config/locales/admin_ui_en.yml +++ b/config/locales/admin_ui_en.yml @@ -33,11 +33,26 @@ en: footer: developed_by: Developed by powered_by: and Powered by + form_actions: + back: Back without saving + create: Create + update: Update pages: index: title: Listing pages + no_items: "There are no pages for now. Just click here to create the first one." + new: new page + formtastic: + titles: + information: General information + meta: Meta + hints: + page: + keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO." + description: "Meta description used within the head tag of the page. Required for SEO." + buttons: login: Log in send_password: Send diff --git a/doc/NICE_TO_HAVE b/doc/NICE_TO_HAVE new file mode 100644 index 00000000..376ee81a --- /dev/null +++ b/doc/NICE_TO_HAVE @@ -0,0 +1,3 @@ +- 2 modes for editing a page + - admin mode => pure html / liquid + - editor: only "editable" tags can be edited ! \ No newline at end of file diff --git a/doc/site.txt b/doc/locomotive/site.txt similarity index 100% rename from doc/site.txt rename to doc/locomotive/site.txt diff --git a/lib/core_ext.rb b/lib/core_ext.rb new file mode 100644 index 00000000..03ef9a17 --- /dev/null +++ b/lib/core_ext.rb @@ -0,0 +1,24 @@ +## String +class String + # def perma_string(sep = '_') + # ActiveSupport::Inflector.parameterize(self, sep).to_s + # end + + def slugify(options = {}) + options = { :sep => '_', :without_extension => false }.merge(options) + # replace accented chars with ther ascii equivalents + s = ActiveSupport::Inflector.transliterate(self).to_s + # No more than one slash in a row + s.gsub!(/(\/[\/]+)/, '/') + # Remove leading or trailing space + s.strip! + # Remove leading or trailing slash + s.gsub! /(^[\/]+)|([\/]+$)/, '' + # Remove extensions + s.gsub! /(\.[a-zA-Z]{2,})/, '' if options[:without_extension] + # Turn unwanted chars into the seperator + s.gsub!(/[^a-zA-Z0-9\-_\+\/]+/i, options[:sep]) + s + end + +end \ No newline at end of file diff --git a/lib/locomotive.rb b/lib/locomotive.rb index 4c9eacc0..f58dd580 100644 --- a/lib/locomotive.rb +++ b/lib/locomotive.rb @@ -17,67 +17,4 @@ module Locomotive yield(self.config) end - # class Configuration - # - # @@defaults = { - # :name => 'LocomotiveApp' - # :default_domain => 'rails.local.fr', - # :reserved_subdomains => %w{www admin email blog webmail mail support help site sites} - # } - # - # cattr_accessor :settings - # - # def initialize - # @@settings = self.class.get_from_hash(@@defaults) - # end - # - # def self.settings - # @@settings - # end - # - # def method_missing(name, *args, &block) - # self.settings.send(name, *args, &block) - # end - # - # protected - # - # # converts a hash map into a ConfigurationHash - # def self.get_from_hash(hash) - # config = ConfigurationHash.new - # - # hash.each_pair do |key, value| - # config[key] = value.is_a?(Hash) ? self.get_from_hash(value) : value - # end - # - # config - # end - # end - # - # # specialized hash for storing configuration settings - # class ConfigurationHash < Hash - # # ensure that default entries always produce - # # instances of the ConfigurationHash class - # def default(key=nil) - # include?(key) ? self[key] : self[key] = self.class.new - # end - # - # # retrieves the specified key and yields it - # # if a block is provided - # def [](key, &block) - # block_given? ? yield(super(key)) : super(key) - # end - # - # # provides member-based access to keys - # # i.e. params.id === params[:id] - # # note: all keys are converted to symbols - # def method_missing(name, *args, &block) - # if name.to_s.ends_with? '=' - # send :[]=, name.to_s.chomp('=').to_sym, *args - # else - # send(:[], name.to_sym, &block) - # end - # end - # end - # - end \ No newline at end of file diff --git a/lib/locomotive/configuration.rb b/lib/locomotive/configuration.rb index e95a4fe9..bb580126 100644 --- a/lib/locomotive/configuration.rb +++ b/lib/locomotive/configuration.rb @@ -5,7 +5,8 @@ module Locomotive @@defaults = { :name => 'LocomotiveApp', :default_domain => 'rails.local.fr', - :reserved_subdomains => %w{www admin email blog webmail mail support help site sites} + :reserved_subdomains => %w{www admin email blog webmail mail support help site sites}, + :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api} } cattr_accessor :settings diff --git a/lib/misc_form_builder.rb b/lib/misc_form_builder.rb new file mode 100644 index 00000000..3ac2fcd3 --- /dev/null +++ b/lib/misc_form_builder.rb @@ -0,0 +1,26 @@ +class MiscFormBuilder < Formtastic::SemanticFormBuilder + + @@all_fields_required_by_default = false + + def foldable_inputs(*args, &block) + opts = args.extract_options! + + unfolded = !(opts[:class] || '').index('off').nil? || @object.new_record? || !@object.errors.empty? + + opts[:class] = (opts[:class] || '') + " inputs foldable #{'folded' unless unfolded}" + args.push(opts) + self.inputs(*args, &block) + end + + def custom_input(name, options = {}, &block) + default_options = { :css => '', :with_label => true, :label => nil } + options = default_options.merge(options) + + html = options[:with_label] ? self.label(options[:label] || name) : '' + html += template.capture(&block) || '' + html += self.errors_on(name) || '' + + template.content_tag(:li, html, :class => "#{options[:css]} #{'error' unless @object.errors[name].empty?}") + end + +end diff --git a/public/images/admin/form/big_item.png b/public/images/admin/form/big_item.png new file mode 100644 index 00000000..efef68fc Binary files /dev/null and b/public/images/admin/form/big_item.png differ diff --git a/public/images/admin/form/error-arrow.png b/public/images/admin/form/error-arrow.png new file mode 100644 index 00000000..9a31f1f2 Binary files /dev/null and b/public/images/admin/form/error-arrow.png differ diff --git a/public/images/admin/form/field.png b/public/images/admin/form/field.png new file mode 100644 index 00000000..053b3942 Binary files /dev/null and b/public/images/admin/form/field.png differ diff --git a/public/images/admin/form/folded-arrow-off.png b/public/images/admin/form/folded-arrow-off.png new file mode 100644 index 00000000..0826f155 Binary files /dev/null and b/public/images/admin/form/folded-arrow-off.png differ diff --git a/public/images/admin/form/folded-arrow-on.png b/public/images/admin/form/folded-arrow-on.png new file mode 100644 index 00000000..1af5d97f Binary files /dev/null and b/public/images/admin/form/folded-arrow-on.png differ diff --git a/public/images/admin/form/folded.png b/public/images/admin/form/folded.png new file mode 100644 index 00000000..644b09c0 Binary files /dev/null and b/public/images/admin/form/folded.png differ diff --git a/public/images/admin/form/footer.png b/public/images/admin/form/footer.png new file mode 100644 index 00000000..ede948cc Binary files /dev/null and b/public/images/admin/form/footer.png differ diff --git a/public/images/admin/form/growl-error.png b/public/images/admin/form/growl-error.png new file mode 100644 index 00000000..b55a414f Binary files /dev/null and b/public/images/admin/form/growl-error.png differ diff --git a/public/images/admin/form/growl-notice.png b/public/images/admin/form/growl-notice.png new file mode 100644 index 00000000..6d24025c Binary files /dev/null and b/public/images/admin/form/growl-notice.png differ diff --git a/public/images/admin/form/header-first-on.png b/public/images/admin/form/header-first-on.png new file mode 100644 index 00000000..73094940 Binary files /dev/null and b/public/images/admin/form/header-first-on.png differ diff --git a/public/images/admin/form/header-on.png b/public/images/admin/form/header-on.png new file mode 100644 index 00000000..116760b8 Binary files /dev/null and b/public/images/admin/form/header-on.png differ diff --git a/public/images/admin/form/header.png b/public/images/admin/form/header.png new file mode 100644 index 00000000..d52df800 Binary files /dev/null and b/public/images/admin/form/header.png differ diff --git a/public/images/admin/form/icons/drag.png b/public/images/admin/form/icons/drag.png new file mode 100644 index 00000000..55fbd935 Binary files /dev/null and b/public/images/admin/form/icons/drag.png differ diff --git a/public/images/admin/form/icons/spinner.gif b/public/images/admin/form/icons/spinner.gif new file mode 100644 index 00000000..b6a7f640 Binary files /dev/null and b/public/images/admin/form/icons/spinner.gif differ diff --git a/public/images/admin/form/icons/trash.png b/public/images/admin/form/icons/trash.png new file mode 100644 index 00000000..54ecdaf2 Binary files /dev/null and b/public/images/admin/form/icons/trash.png differ diff --git a/public/images/admin/form/item.png b/public/images/admin/form/item.png new file mode 100644 index 00000000..9d3eee9f Binary files /dev/null and b/public/images/admin/form/item.png differ diff --git a/public/images/admin/form/pen.png b/public/images/admin/form/pen.png new file mode 100644 index 00000000..f8d4f8d2 Binary files /dev/null and b/public/images/admin/form/pen.png differ diff --git a/public/images/admin/list/empty.png b/public/images/admin/list/empty.png new file mode 100644 index 00000000..8e5b4067 Binary files /dev/null and b/public/images/admin/list/empty.png differ diff --git a/public/images/admin/list/icons/drag.png b/public/images/admin/list/icons/drag.png new file mode 100644 index 00000000..150a7c1f Binary files /dev/null and b/public/images/admin/list/icons/drag.png differ diff --git a/public/images/admin/list/item-left.png b/public/images/admin/list/item-left.png new file mode 100644 index 00000000..666c09ff Binary files /dev/null and b/public/images/admin/list/item-left.png differ diff --git a/public/images/admin/list/item-right.png b/public/images/admin/list/item-right.png new file mode 100644 index 00000000..2ab745d7 Binary files /dev/null and b/public/images/admin/list/item-right.png differ diff --git a/public/images/admin/list/item.png b/public/images/admin/list/item.png new file mode 100644 index 00000000..cfdff1e9 Binary files /dev/null and b/public/images/admin/list/item.png differ diff --git a/public/images/admin/list/none.png b/public/images/admin/list/none.png new file mode 100644 index 00000000..bf1fc766 Binary files /dev/null and b/public/images/admin/list/none.png differ diff --git a/public/images/admin/list/thumb.png b/public/images/admin/list/thumb.png new file mode 100644 index 00000000..e54fe0ec Binary files /dev/null and b/public/images/admin/list/thumb.png differ diff --git a/public/images/admin/plugins/toggle_handle-bg.png b/public/images/admin/plugins/toggle_handle-bg.png new file mode 100644 index 00000000..52816fdb Binary files /dev/null and b/public/images/admin/plugins/toggle_handle-bg.png differ diff --git a/public/images/admin/plugins/toggle_handle_left-bg.png b/public/images/admin/plugins/toggle_handle_left-bg.png new file mode 100644 index 00000000..90a4cf62 Binary files /dev/null and b/public/images/admin/plugins/toggle_handle_left-bg.png differ diff --git a/public/images/admin/plugins/toggle_handle_right-bg.png b/public/images/admin/plugins/toggle_handle_right-bg.png new file mode 100644 index 00000000..aa6d30cd Binary files /dev/null and b/public/images/admin/plugins/toggle_handle_right-bg.png differ diff --git a/public/images/admin/plugins/toggle_shadow-bg.png b/public/images/admin/plugins/toggle_shadow-bg.png new file mode 100644 index 00000000..148b8089 Binary files /dev/null and b/public/images/admin/plugins/toggle_shadow-bg.png differ diff --git a/public/javascripts/admin/application.js b/public/javascripts/admin/application.js new file mode 100644 index 00000000..dc6bfabc --- /dev/null +++ b/public/javascripts/admin/application.js @@ -0,0 +1,63 @@ +var I18nLocale = null; + +/* ___ growl ___ */ + +$.growl.settings.noticeTemplate = '' + + '
' + + '

%message%

' + + '
'; + +$.growl.settings.dockCss = { + position: 'fixed', + bottom: '20px', + left: '0px', + width: '100%', + zIndex: 50000 +}; + +/* ___ global ___ */ + +$(document).ready(function() { + I18nLocale = $('meta[name=locale]').attr('content'); + + // form + $('.formtastic li input, .formtastic li textarea').focus(function() { + $('.formtastic li.error p.inline-errors').fadeOut(200); + if ($(this).parent().hasClass('error')) { + $(this).nextAll('p.inline-errors').show(); + } + }); + $('.formtastic li.error input').eq(0).focus(); + + // editable title (page, ...etc) + $('#content h2 a.editable').each(function() { + var target = $('#' + $(this).attr('rel')), + hint = $(this).attr('title'); + + target.parent().hide(); + + $(this).click(function(event) { + var newValue = prompt(hint, $(this).html()); + if (newValue && newValue != '') { + $(this).html(newValue); + target.val(newValue); + } + event.preventDefault(); + }); + }); + + // foldable + $('.formtastic fieldset.foldable legend span').append(' '); + $('.formtastic fieldset.foldable.folded ol').hide(); + $('.formtastic fieldset.foldable legend').click(function() { + var parent = $(this).parent(), content = $(this).next(); + if (parent.hasClass('folded')) { + parent.removeClass('folded'); + content.slideDown(400, function() { }); + } else + content.slideUp(400, function() { parent.addClass('folded'); }); + }); + + // nifty checkboxes + $('.formtastic li.toggle input[type=checkbox]').checkToggle(); +}); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/growl.js b/public/javascripts/admin/plugins/growl.js new file mode 100644 index 00000000..b0767876 --- /dev/null +++ b/public/javascripts/admin/plugins/growl.js @@ -0,0 +1,143 @@ +/* + * jQuery Growl plugin + * Version 1.0.1 (10/27/2008) + * @requires jQuery v1.2.3 or later + * + * Examples at: http://fragmentedcode.com/jquery-growl + * Copyright (c) 2008 David Higgins + * + * Special thanks to Daniel Mota for inspiration: + * http://icebeat.bitacoras.com/mootools/growl/ + */ + +/* +USAGE: + + $.growl(title, msg); + $.growl(title, msg, image); + $.growl(title, msg, image, priority); + +THEME/SKIN: + +You can override the default look and feel by updating these objects: +$.growl.settings.displayTimeout = 4000; +$.growl.settings.noticeTemplate = '' + + '
' + + '
' + + '
' + + ' ' + + '

%title%

' + + '

%message%

' + + '
' + + '
' + + '
'; +$.growl.settings.noticeCss = { + position: 'relative' +}; + +To change the 'dock' look, and position: + +$.growl.settings.dockTemplate = '
'; +$.growl.settings.dockCss = { + position: 'absolute', + top: '10px', + right: '10px', + width: '300px' + }; + +The dockCss will allow you to 'dock' the notifications to a specific area +on the page, such as TopRight (the default) or TopLeft, perhaps even in a +smaller area with "overflow: scroll" enabled? +*/ + +(function($) { + +$.growl = function(title,message,image,priority) { notify(title,message,image,priority); } +$.growl.version = "1.0.0-b2"; + +function create(rebuild) { + var instance = document.getElementById('growlDock'); + if(!instance || rebuild) { + instance = $(jQuery.growl.settings.dockTemplate).attr('id', 'growlDock').addClass('growl'); + if(jQuery.growl.settings.defaultStylesheet) { + $('head').append(''); + } + + } else { + instance = $(instance); + } + $('body').append(instance.css(jQuery.growl.settings.dockCss)); + return instance; +}; + +function r(text, expr, val) { + while(expr.test(text)) { + text = text.replace(expr, val); + } + return text; +}; + +function notify(title,message,image,priority) { + var instance = create(); + var html = jQuery.growl.settings.noticeTemplate; + if(typeof(html) == 'object') html = $(html).html(); + html = r(html, /%message%/, (message?message:'')); + html = r(html, /%title%/, (title?title:'')); + html = r(html, /%image%/, (image?image:jQuery.growl.settings.defaultImage)); + html = r(html, /%priority%/, (priority?priority:'normal')); + + var notice = $(html) + .hide() + .css(jQuery.growl.settings.noticeCss) + .fadeIn(jQuery.growl.settings.notice);; + + $.growl.settings.noticeDisplay(notice); + instance.append(notice); + $('a[@rel="close"]', notice).click(function() { + notice.remove(); + }); + if ($.growl.settings.displayTimeout > 0) { + setTimeout(function(){ + jQuery.growl.settings.noticeRemove(notice, function(){ + notice.remove(); + }); + }, jQuery.growl.settings.displayTimeout); + } +}; + + +// default settings +$.growl.settings = { + dockTemplate: '
', + dockCss: { + position: 'fixed', + top: '10px', + right: '10px', + width: '300px', + zIndex: 50000 + }, + noticeTemplate: + '
' + + '

%title%

' + + '

%message%

' + + '
', + noticeCss: { + opacity: 1, + backgroundColor: 'transparent', + color: '#ffffff' + }, + noticeDisplay: function(notice) { + notice.css({'opacity':'0'}).fadeIn(jQuery.growl.settings.noticeFadeTimeout); + }, + noticeRemove: function(notice, callback) { + notice.animate({opacity: '0', height: '0px'}, {duration:jQuery.growl.settings.noticeFadeTimeout, complete: callback}); + }, + noticeFadeTimeout: 'slow', + displayTimeout: 3500, + defaultImage: 'growl.jpg', + defaultStylesheet: null, + noticeElement: function(el) { + $.growl.settings.noticeTemplate = $(el); + } +}; +})(jQuery); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/toggle.js b/public/javascripts/admin/plugins/toggle.js new file mode 100644 index 00000000..bf560f16 --- /dev/null +++ b/public/javascripts/admin/plugins/toggle.js @@ -0,0 +1,109 @@ +/** + * + * Copyright (c) 2009 Tony Dewan (http://www.tonydewan.com/) + * Licensed under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + * Project home: + * http://www.tonydewan.com/code/checkToggle/ + * + */ + +(function($) { + /** + * Version 1.0 + * Replaces checkboxes with a toggle switch. + * usage: $("input[type='checkbox']").checkToggle(settings); + * + * @name checkToggle + * @type jquery + * @param Hash settings Settings + * @param String settings[on_label] Text used for the left-side (on) label. Defaults to "On" + * @param String settings[off_label] Text used for the right-side (off) label. Defaults to "Off" + * @param String settings[on_bg_color] Hex background color for On state + * @param String settings[off_bg_color] Hex background color for Off state + * @param String settings[skin_dir] Document relative (or absolute) path to the skin directory + * @param Bool settings[bypass_skin] Flags whether to bypass the inclusion of the skin.css file. Used if you've included the skin styles somewhere else already. + */ + + $.fn.checkToggle = function(settings) { + + settings = $.extend({ + on_label : 'Yes', + on_bg_color : '#8FE38D', + off_label : 'No', + off_bg_color: '#F8837C', + skin_dir : "skin/", + bypass_skin : false + }, settings); + + // FIXME (Didier Lafforgue) it works but it doesn't scale if we handle another locale + if (typeof I18nLocale != 'undefined' && I18nLocale == 'fr') { + settings.on_label = 'Oui'; + settings.off_label = 'Non'; + } + + // append the skin styles + // if(settings.bypass_skin == false){ + // $("head").append(''); + // } + + function toggle(element){ + + var checked = $(element).parent().parent().prev().attr("checked"); + + // if it's set to on + if(checked){ + + $(element).animate({marginLeft: '34px'}, 100, + + // callback function + function(){ + $(element).parent().prev().css("color","#cccccc"); + $(element).parent().next().css("color","#333333"); + $(element).parent().css("background-color", settings.off_bg_color); + $(element).parent().parent().prev().removeAttr("checked"); + $(element).removeClass("left").addClass("right"); + }); + + }else{ + + $(element).animate({marginLeft: '0em'}, 100, + + // callback function + function(){ + $(element).parent().prev().css("color","#333333"); + $(element).parent().next().css("color","#cccccc"); + $(element).parent().css("background-color", settings.on_bg_color); + $(element).parent().parent().prev().attr("checked","checked"); + $(element).removeClass("right").addClass("left"); + + }); + + } + + }; + + return this.each(function () { + + if ($(this).hasClass('simple')) return; + + // hide the checkbox + $(this).css('display','none'); + + // insert the new toggle markup + if($(this).attr("checked") == true){ + $(this).after('
'+settings.on_label+'<\/span>
<\/span><\/div>'+settings.off_label+'<\/span><\/div>'); + }else{ + $(this).after('
'+settings.on_label+'<\/span>
<\/span><\/div>'+settings.off_label+'<\/span><\/div>'); + } + + // Bind the switchHandle click events to the internal toggle function + $(this).next().find('div.switchArea').bind("click", function () { + toggle($(this).find('.switchHandle')); }) + }); + + + }; + +})(jQuery); \ No newline at end of file diff --git a/public/javascripts/application.js b/public/javascripts/application.js deleted file mode 100644 index fe457769..00000000 --- a/public/javascripts/application.js +++ /dev/null @@ -1,2 +0,0 @@ -// Place your application-specific JavaScript functions and classes here -// This file is automatically included by javascript_include_tag :defaults diff --git a/public/stylesheets/admin/application.css b/public/stylesheets/admin/application.css index e69de29b..13e72de6 100644 --- a/public/stylesheets/admin/application.css +++ b/public/stylesheets/admin/application.css @@ -0,0 +1,243 @@ +/* ___ application messages ___ */ + +div.notice { + background: transparent url(/images/admin/form/growl-notice.png) repeat-x 0 0; + position: relative; + width: 100%; + height: 90px; +} + +div.notice.error { + background-image: url(/images/admin/form/growl-error.png); +} + +div.notice p { + position: relative; + top: 35px; + margin: 0px; + text-align: center; + font-size: 1.5em; + text-shadow: 1px 1px 1px #333; + color: #fff; +} + +/* ___ list ___ */ + +p.no-items { + padding: 15px 0px; + background: transparent url(/images/admin/list/none.png) no-repeat 0 0; + text-align: center; + color: #9d8963 !important; + font-size: 1.1em !important; +} + +p.no-items a { + color: #ff2900; + text-decoration: none; +} + +p.no-items a:hover { + text-decoration: underline; +} + +ul.list { + list-style: none; + margin: 0px 0 20px 0; + background: white; +} + +ul.list li { + height: 31px; + margin-bottom: 10px; + position: relative; + clear: both; + background: transparent url(/images/admin/list/item.png) no-repeat 0 0; +} + +ul.list li strong a { + position: relative; + top: 2px; + left: 15px; + text-decoration: none; + color: #1f82bc; + font-size: 0.9em; +} + +ul.list.sortable li strong a { left: 10px; } + +ul.list li strong a:hover { text-decoration: underline; } + +ul.list li div.more { + position: absolute; + top: 3px; + right: 15px; + font-size: 0.7em; + color: #8b8d9a; +} + +ul.list li div.more a { + margin-left: 10px; + position: relative; + top: 4px; +} + +ul.list li span.handle { + position: relative; + top: 5px; + margin: 0 0 0 15px; + cursor: move; +} + +/* ___ assets ___ */ + +ul.assets { + list-style: none; + margin: 0px; + padding: 0px; +} + +ul.assets li.asset { + position: relative; + float: left; + width: 139px; + height: 140px; + background: transparent url(/images/admin/list/thumb.png) no-repeat 0 0; + margin: 0 17px 17px 0; +} + +ul.assets li.asset.last { + margin-right: 0px; +} + +ul.assets li.asset h4 { margin: 0px; height: 30px; } + +ul.assets li.asset h4 a { + position: relative; + top: 6px; + left: 12px; + font-weight: bold; + font-size: 0.7em; + color: #1f82bc; + text-decoration: none; +} + +ul.assets li.asset h4 a:hover { text-decoration: underline; } + +ul.assets li.asset div.image { + width: 80px; + height: 80px; + border: 4px solid #fff; + margin: 10px 0 0 24px; + background: transparent url(/images/admin/list/empty.png) repeat 0 0; +} + +ul.assets li.asset div.image div.inside { + display: table-cell; + vertical-align: middle; + text-align: center; + width: 80px; + height: 80px; +} + +ul.assets li.asset div.actions { + position: absolute; + top: 8px; + right: 12px; +} + +/* ___ asset collections ___ */ + +div#asset-uploader { display: inline-block; margin-left: 10px; } +div#asset-uploader span.spinner { position: relative; top: -3px; display: none; } +div#uploadAssetsInputQueue { display: none; } + + +/* ___ pages ___ */ + +#pages-list { + list-style: none; + margin: 0px 0 20px 0; + background: white; +} + +#pages-list li { + height: 31px; + margin-bottom: 10px; + position: relative; + clear: both; +} + +#pages-list li em { + display: block; + float: left; + background: transparent url(/images/admin/list/item-left.png) no-repeat left 0; + height: 31px; + width: 18px; +} + +#pages-list li .toggler { + position: absolute; + top: 9px; + left: -15px; + cursor: pointer; +} + +#pages-list li strong { + margin-left: 18px; + display: block; + height: 31px; + background: transparent url(/images/admin/list/item-right.png) no-repeat right 0; +} + +#pages-list li strong a { + position: relative; + top: 3px; + text-decoration: none; + color: #1f82bc; + font-size: 0.9em; + padding-left: 6px; +} + +#pages-list li strong a:hover { text-decoration: underline; } + +#pages-list li.hidden strong a { font-style: italic; font-weight: normal; } + +#pages-list li .more { + position: absolute; + top: 3px; + right: 20px; + font-size: 0.7em; + color: #8b8d9a; +} + +#pages-list li .more a { + position: relative; + top: 3px; + margin-left: 10px; + outline: none; +} + +#pages-list li.error .more { top: 16px; } + + +#pages-list li.depth-1 { margin-left: 40px; } +#pages-list li.depth-1.index { margin-left: 0px; } +#pages-list li.depth-1.error { border-top: 1px dotted #bbbbbd; padding-top: 10px; margin-left: 0px; } + +#pages-list li.depth-2 { margin-left: 80px; } +#pages-list li.depth-2.index { margin-left: 40px; } + +#pages-list li.depth-3 { margin-left: 120px; } +#pages-list li.depth-3.index { margin-left: 80px; } + +#pages-list li.depth-4 { margin-left: 160px; } +#pages-list li.depth-4.index { margin-left: 120px; } + +#pages-list li.depth-5 { margin-left: 200px; } +#pages-list li.depth-5.index { margin-left: 160px; } + +/* ___ Progress bar ___ */ + +#progressbar-wrapper { margin: 40px 0; height: 30px; } +#progressbar-wrapper #progressbar { height: 100%; } + diff --git a/public/stylesheets/admin/buttons.css b/public/stylesheets/admin/buttons.css index e69de29b..8d2a4df9 100644 --- a/public/stylesheets/admin/buttons.css +++ b/public/stylesheets/admin/buttons.css @@ -0,0 +1,62 @@ +.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; +} + +@media screen and (-webkit-min-device-pixel-ratio:0) { + .button { padding-left: 5px; } + .button span { top: 0px; } +} + +.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: transparent url(/images/admin/buttons/action-left.png) no-repeat left -40px; + color: #787a89; + height: 20px; + font-size: 0.7em; + padding: 0px 0px 0px 12px; + color: #8B8D9A !important; + text-decoration: none; +} + +.button.small span { + background-image: url(/images/admin/buttons/action-right.png); + text-shadow: 1px 1px 1px #fff; + padding: 0px 12px 10px 0px; + top: 0px; + color: #8B8D9A; +} + +.button.remove, .button.remove span { + color: #ff092c !important; + font-size: 1.1em; +} + +.button.remove:hover span { text-decoration: underline; } \ No newline at end of file diff --git a/public/stylesheets/admin/formtastic.css b/public/stylesheets/admin/formtastic.css index e69de29b..8e6ce692 100644 --- a/public/stylesheets/admin/formtastic.css +++ b/public/stylesheets/admin/formtastic.css @@ -0,0 +1,137 @@ +/* ------------------------------------------------------------------------------------------------- + +It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after +this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs. +This will allow you to update formtastic.css with new releases without clobbering your own changes. + +This stylesheet forms part of the Formtastic Rails Plugin +(c) 2008 Justin French + +--------------------------------------------------------------------------------------------------*/ + + +/* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just form.formtastic +--------------------------------------------------------------------------------------------------*/ +form.formtastic, form.formtastic ul, form.formtastic ol, form.formtastic li, form.formtastic fieldset, form.formtastic legend, form.formtastic input, form.formtastic textarea, form.formtastic select, form.formtastic p { margin:0; padding:0; } +form.formtastic fieldset { border:0; } +form.formtastic em, form.formtastic strong { font-style:normal; font-weight:normal; } +form.formtastic ol, form.formtastic ul { list-style:none; } +form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; } +form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; } +form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; } +form.formtastic legend { color:#000; } + + +/* FIELDSETS & LISTS +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset { } +form.formtastic fieldset.inputs { } +form.formtastic fieldset.buttons { padding-left:25%; } +form.formtastic fieldset ol { } +form.formtastic fieldset.buttons li { float:left; padding-right:0.5em; } + +/* clearfixing the fieldsets */ +form.formtastic fieldset { display: inline-block; } +form.formtastic fieldset:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +html[xmlns] form.formtastic fieldset { display: block; } +* html form.formtastic fieldset { height: 1%; } + + +/* INPUT LIs +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li { margin-bottom:1.5em; } + +/* clearfixing the li's */ +form.formtastic fieldset ol li { display: inline-block; } +form.formtastic fieldset ol li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +html[xmlns] form.formtastic fieldset ol li { display: block; } +* html form.formtastic fieldset ol li { height: 1%; } + +form.formtastic fieldset ol li.required { } +form.formtastic fieldset ol li.optional { } +form.formtastic fieldset ol li.error { } + + +/* LABELS +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li label { display:block; width:25%; float:left; padding-top:.2em; } +form.formtastic fieldset ol li li label { line-height:100%; padding-top:0; } +form.formtastic fieldset ol li li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;} + + +/* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets) +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li fieldset { position:relative; } +form.formtastic fieldset ol li fieldset legend { position:absolute; width:25%; padding-top:0.1em; } +form.formtastic fieldset ol li fieldset legend span { position:absolute; } +form.formtastic fieldset ol li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; } +form.formtastic fieldset ol li fieldset ol li { padding:0; border:0; } + + +/* INLINE HINTS +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li p.inline-hints { color:#666; margin:0.5em 0 0 25%; } + + +/* INLINE ERRORS +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; } +form.formtastic fieldset ol li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; } +form.formtastic fieldset ol li ul.errors li { padding:0; border:none; display:list-item; } + + +/* STRING & NUMERIC OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.string input { width:74%; } +form.formtastic fieldset ol li.password input { width:74%; } +form.formtastic fieldset ol li.numeric input { width:74%; } + + +/* TEXTAREA OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.text textarea { width:74%; } + + +/* HIDDEN OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.hidden { display:none; } + + +/* BOOLEAN OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.boolean label { padding-left:25%; width:auto; } +form.formtastic fieldset ol li.boolean label input { margin:0 0.5em 0 0.2em; } + + +/* RADIO OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.radio { } +form.formtastic fieldset ol li.radio fieldset ol { margin-bottom:-0.6em; } +form.formtastic fieldset ol li.radio fieldset ol li { margin:0.1em 0 0.5em 0; } +form.formtastic fieldset ol li.radio fieldset ol li label { float:none; width:100%; } +form.formtastic fieldset ol li.radio fieldset ol li label input { margin-right:0.2em; } + + +/* CHECK BOXES (COLLECTION) OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.check_boxes { } +form.formtastic fieldset ol li.check_boxes fieldset ol { margin-bottom:-0.6em; } +form.formtastic fieldset ol li.check_boxes fieldset ol li { margin:0.1em 0 0.5em 0; } +form.formtastic fieldset ol li.check_boxes fieldset ol li label { float:none; width:100%; } +form.formtastic fieldset ol li.check_boxes fieldset ol li label input { margin-right:0.2em; } + + + +/* DATE & TIME OVERRIDES +--------------------------------------------------------------------------------------------------*/ +form.formtastic fieldset ol li.date fieldset ol li, +form.formtastic fieldset ol li.time fieldset ol li, +form.formtastic fieldset ol li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; } + +form.formtastic fieldset ol li.date fieldset ol li label, +form.formtastic fieldset ol li.time fieldset ol li label, +form.formtastic fieldset ol li.datetime fieldset ol li label { display:none; } + +form.formtastic fieldset ol li.date fieldset ol li label input, +form.formtastic fieldset ol li.time fieldset ol li label input, +form.formtastic fieldset ol li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; } diff --git a/public/stylesheets/admin/formtastic_changes.css b/public/stylesheets/admin/formtastic_changes.css index e69de29b..343e1f47 100644 --- a/public/stylesheets/admin/formtastic_changes.css +++ b/public/stylesheets/admin/formtastic_changes.css @@ -0,0 +1,436 @@ +/* ------------------------------------------------------------------------------------------------- + +Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs. +This will allow you to update formtastic.css with new releases without clobbering your own changes. + +For example, to make the inline hint paragraphs a little darker in color than the standard #666: + +form.formtastic fieldset ol li p.inline-hints { color:#333; } + +--------------------------------------------------------------------------------------------------*/ + +form.formtastic legend { + margin: 0; + float: left; + white-space: normal; + *margin-left: -7px; +} + +form.formtastic legend span { + display: block; + width: 900px; + height: 26px; + background: transparent url(/images/admin/form/header.png) no-repeat 0 0px; + color: #1e1f26; + font-size: 0.7em; + padding: 4px 0 0 20px; +} + +/* ___ enabling fold/unfold ___ */ + +form.formtastic fieldset.foldable legend span { cursor: pointer; } + +form.formtastic fieldset.foldable legend span em { + display: inline-block; + width: 9px; + height: 6px; + position: relative; + top: 8px; + left: 10px; + background: transparent url(/images/admin/form/folded-arrow-on.png) no-repeat 0 0px; +} + +form.formtastic fieldset.foldable.folded legend span { background-image: url(/images/admin/form/folded.png); } +form.formtastic fieldset.foldable.folded legend span em { + width: 6px; + height: 9px; + top: 6px; + background-image: url(/images/admin/form/folded-arrow-off.png); +} + +form.formtastic fieldset.foldable ol { + clear: both; + width: 100%; + overflow: hidden; +} + +form.formtastic fieldset.foldable.folded ol { display: none; } + +@media screen and (-webkit-min-device-pixel-ratio:0) { + form.formtastic fieldset.foldable legend span em { top: 0px; } + form.formtastic fieldset.foldable.folded legend span em { top: 0px; } +} + +/* ___ inputs ___ */ + +form.formtastic fieldset.inputs { min-height: 30px; width: 100%; margin-bottom: 20px; } + +form.formtastic fieldset.inputs ol { + margin: 30px 0 0 0; + padding-top: 15px; + padding-bottom: 5px; + background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom; +} + +@media screen and (-webkit-min-device-pixel-ratio:0) { + form.formtastic fieldset.inputs ol { + margin-top: 30px; + } +} + +form.formtastic fieldset ol li { width: 100%; position: relative; margin-bottom: 1.3em; } + +form.formtastic fieldset ol li label { text-align: left; padding: 0.3em 2em 0 20px; font-size: 0.8em; color: #17171b; width: 15%; } + +form.formtastic fieldset ol li.string input, +form.formtastic fieldset ol li.password input, +form.formtastic fieldset ol li.numeric input, +form.formtastic fieldset ol li.text textarea, +form.formtastic fieldset ol li code textarea, +form.formtastic fieldset ol li input[type=password] { + padding: 4px; + font-size: 0.9em; + width: 45%; + color: #787a89; + background: white url(/images/admin/form/field.png) repeat-x 0 0; + border: 1px solid #a6a8b8; +} + +form.formtastic fieldset ol li p.inline-hints { margin-left: 20%; } +form.formtastic fieldset ol li p.inline-hints a { color: #1f82bc; } + +form.formtastic fieldset ol li code { display: block; border: 1px solid #a6a8b8; margin: 10px 20px 0 20px; } +form.formtastic fieldset ol li code.nude textarea { + width: 870px; + border: 0px; +} + +/*form.formtastic fieldset ol li code.html iframe { width: 46% !important; }*/ + +form.formtastic fieldset ol li select { font-size: 0.9em; position: relative; top: 2px; color: #787a89; } + +form.formtastic fieldset ol li.error input, +form.formtastic fieldset ol li.error textarea, +form.formtastic fieldset ol li.error code iframe { border: 2px solid #ec3f48 !important; } +form.formtastic fieldset ol li.error code { border: none; } +form.formtastic fieldset ol li p.inline-errors { + display: none; + position: absolute; + top: 0px; + left: 630px; + width: 250px; + margin: 0px; + padding: 6px 5px 8px 25px; + background: #ec3f48 url(/images/admin/form/error-arrow.png) no-repeat 0 0; + color: #fff !important; + font-size: 0.7em !important; +} + + +/*form.formtastic hr { border-top: 2px solid #ccc; }*/ + +/*form.formtastic fieldset.buttons { padding-left: 28%; padding-bottom: 20px; }*/ + +form.formtastic div.actions { + position: relative; + top: 27px; + left: -15px; + width: 950px; + background: #8b8d9a; +} + +form.formtastic div.actions p { + padding: 15px; + margin: 0px; +} + +form.formtastic div.actions a { + color: #fff; + text-decoration: none; + font-size: 0.8em; + position: relative; + top: 4px; +} + +form.formtastic div.actions p a:hover { text-decoration: underline; } + +form.formtastic div.actions .last p { text-align: right; } + + +/* ___ pages ___ */ + +form.formtastic fieldset ol li.path em { + font-size: 0.8em; +} + +form.formtastic fieldset ol li.path input { + background: transparent; + padding: 4px 4px 2px 4px; + border: none; + color: #787a89; + border-bottom: 1px solid #b5b7c4; + width: 30%; +} + +form.formtastic fieldset ol li.path.error input { + border: none !important; + border-bottom: 2px solid #ff092c !important; +} + +/* ___ sites ___ */ + +form.formtastic fieldset ol li.item { + position: relative; + background: transparent url(/images/admin/form/item.png) no-repeat 0 0; + height: 25px; + width: 861px; + margin: 0px 0px 10px 20px; + padding: 3px 10px; +} + +form.formtastic fieldset ol li.item strong { + font-size: 0.9em; + font-weight: bold; + color: #17171d; +} + +form.formtastic fieldset ol li.item strong a { + color: #17171d; + text-decoration: none; +} + +form.formtastic fieldset ol li.item strong a:hover { text-decoration: underline; } + +form.formtastic fieldset ol li.item em { + margin-left: 10px; + font-size: 0.7em; + color: #757575; +} + +form.formtastic fieldset ol li.item span.actions { + position: absolute; + top: 7px; + right: 10px; + width: 16px; + height: 16px; +} + +/* ___ editable-list (content type fields and validations) ___ */ + +form.formtastic fieldset.editable-list ol { padding-left: 20px; } + +form.formtastic fieldset.editable-list ol li { margin-left: 0px !important; } + +form.formtastic fieldset.editable-list ol li span.handle { + cursor: move; + position: relative; + top: 1px; +} + +form.formtastic fieldset.editable-list ol li.added span.actions a.remove { + display: inline; +} + +form.formtastic fieldset.editable-list ol li.added span.actions button { + display: none; +} + +form.formtastic fieldset.editable-list ol li.added select { + display: none; + position: relative; + top: -1px; +} + +form.formtastic fieldset.editable-list ol li.added em { + color: #8b8d9a; + font-size: 0.9em; + font-style: italic; + margin-left: 3px; +} +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; + cursor: pointer; + color: #17171D; + font-weight: bold; +} + +form.formtastic fieldset.editable-list ol li.added input { + position: relative; + top: -1px; + background: transparent; + border: 1px solid transparent; + padding: 1px 5px 2px 5px; + color: #17171D; + font-size: 0.9em; + font-weight: bold; + cursor: normal; +} + +form.formtastic fieldset.editable-list ol li.added input:hover { + background: #fffbe5; + border: 1px dotted #efe4a5; + cursor: pointer; +} + +form.formtastic fieldset.editable-list ol li.added input:focus { + font-size: 0.9em; + font-weight: normal; + color: #787a89; + background: white url(/images/admin/form/field.png) repeat-x 0 0; + border: 1px solid #a6a8b8; +} + +form.formtastic fieldset.editable-list ol li.new { + height: 42px; + background-image: url(/images/admin/form/big_item.png); + padding-top: 10px; +} + +form.formtastic fieldset.editable-list ol li.new input { + display: inline; + margin-left: 10px; + padding: 4px; + font-size: 0.9em; + width: 180px; + color: #787a89; + background: white url(/images/admin/form/field.png) repeat-x 0 0; + border: 1px solid #a6a8b8; + position: relative; + top: 1px; +} + +form.formtastic fieldset.editable-list ol li.new select { + display: inline; +} + +form.formtastic fieldset.editable-list ol li.new span.handle { + display: none; +} + +form.formtastic fieldset.editable-list ol li.new span.actions { + width: auto; + top: 10px; +} + +form.formtastic fieldset.editable-list ol li.new span.actions a.remove { + display: none; +} + +form.formtastic fieldset.editable-list ol li.new span.actions button { + display: inline; +} + +form.formtastic fieldset.editable-list ol li.new span.actions button span { + font-size: 0.8em; +} + +/* ___ editable-list (content type validations) ___ */ + +form.formtastic fieldset.validations ol li.added em.key { + display: inline-block; + position: relative; + top: -1px; + padding: 1px 5px 2px 5px; + color: #17171D; + font-size: 0.9em; + font-weight: bold; + font-style: normal; + margin-left: 5px; + width: 180px; +} + +/* ___ my account ___ */ + +form.formtastic fieldset.language li.full span { + margin: 0 20px; + font-size: 0.8em; + font-weight: bold; +} + +form.formtastic fieldset.language li.full span img { + position: relative; + top: 6px; +} + +form.formtastic fieldset.language li.full span input { + margin-left: 5px; +} + +/* ___ membership ___ */ + +form.formtastic fieldset.email li.full input { + margin-left: 20px; +} + +/* ___ assets ___ */ + +.selector { + position: relative; +} + +.selector span.alt { + position: absolute; + top: 7px; + right: 20px; + color: #787a89; + font-size: 0.7em; + text-decoration: none; + cursor: pointer; +} + + +form.formtastic fieldset.file li.full input { + margin-left: 20px; +} + +form.formtastic fieldset.file li.full p.inline-errors { display: block !important; } + + +form.formtastic fieldset.preview { position: relative; } + +form.formtastic fieldset.preview li { text-align: center; } + +form.formtastic fieldset.preview li img { margin-top: 10px; border: 4px solid white; } + +form.formtastic fieldset.preview div.size { + position: absolute; + top: 7px; + right: 20px; + color: #787a89; + font-size: 0.7em; +} + +@media screen and (-webkit-min-device-pixel-ratio:0) { + form.formtastic fieldset.preview div.size { + top: 7px; + } +} + +/* ___ main error message ___ */ + +div.form-errors p { + background: #FFE5E5; + color: #CE2525; + font-size: 18px; + font-weight: bold; + padding: 10px; + margin: 0px; + text-align: center; +} + +div.formError { + position: relative; + top: -2px; + display: inline; + background: #CE2525 url(/images/admin/left_arrow_red.png) no-repeat 0px center; + color: white; + font-size: 12px; + font-weight: normal; + padding: 3px 10px 3px 20px; + margin-left: 10px; +} + +div.fieldWithErrors { display: inline; } + diff --git a/public/stylesheets/admin/plugins/toggle.css b/public/stylesheets/admin/plugins/toggle.css new file mode 100644 index 00000000..9a98b82f --- /dev/null +++ b/public/stylesheets/admin/plugins/toggle.css @@ -0,0 +1,42 @@ +.toggleSwitch { + position: relative; + top: 3px; +} + +div.toggleSwitch span.leftLabel{ + float: left; +} + +div.toggleSwitch span.leftLabel, div.toggleSwitch span.rightLabel{ + line-height: 20px; + padding: 0 5px; + font-size: 0.8em; + font-weight: bold; +} + +div.toggleSwitch div.switchArea { + float: left; + background: transparent url("/images/admin/plugins/toggle_shadow-bg.png") top left no-repeat; + width: 64px; + height: 24px; + cursor: pointer; +} + +div.toggleSwitch span.switchHandle{ + display: block; + background: #aaa; + background: transparent url("/images/admin/plugins/toggle_handle-bg.png") top left no-repeat; + width: 30px; + height: 100%; + cursor: pointer; + cursor: hand; + margin-left: 0; +} + +div.toggleSwitch span.switchHandle.left{ + background-image: url("/images/admin/plugins/toggle_handle_left-bg.png"); +} + +div.toggleSwitch span.switchHandle.right{ + background-image: url("/images/admin/plugins/toggle_handle_right-bg.png"); +} diff --git a/spec/factories.rb b/spec/factories.rb index 6e5de74d..84af012a 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -14,3 +14,9 @@ Factory.define :account do |a| a.locale 'en' end +## Pages ## +Factory.define :page do |p| + p.association :site, :factory => :site + p.title 'Home page' + p.path 'index' +end \ No newline at end of file diff --git a/spec/models/page_spec.rb b/spec/models/page_spec.rb new file mode 100644 index 00000000..e8c9af66 --- /dev/null +++ b/spec/models/page_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe Page do + + it 'should have a valid factory' do + Factory.build(:page).should be_valid + end + + ## Validations ## + + %w{site title path}.each do |field| + it "should validate presence of #{field}" do + page = Factory.build(:page, field.to_sym => nil) + page.should_not be_valid + page.errors[field.to_sym].should == ["can't be blank"] + end + end + + it 'should validate uniqueness of path' do + page = Factory(:page) + (page = Factory.build(:page, :site => page.site)).should_not be_valid + page.errors[:path].should == ["is already taken"] + end + + ## Named scopes ## + + ## Associations ## + + ## Methods ## + + describe 'once created' do + + it 'should add the body part' do + page = Factory(:page) + page.parts.should_not be_empty + page.parts.first.name.should == 'body' + end + + end + + +end \ No newline at end of file