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? %>
+/ <%= f.text_area :body %>
+/ <% end %>
+/
+/ <% f.fields_for :parts do |g| %>
+/ <% unless g.object.disabled? %>
+/ <%= g.text_area :body, :class => 'big' %>
+/ <% end %>
+/ <% 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 = '' +
+ '';
+
+$.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