first cucumber features + crud for pages (95% done)
This commit is contained in:
parent
3b847b2732
commit
a0216dc75f
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ db/*.sqlite3
|
|||||||
log/*.log
|
log/*.log
|
||||||
tmp/**/*
|
tmp/**/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
rerun.txt
|
||||||
|
9
Gemfile
9
Gemfile
@ -7,7 +7,8 @@ gem "rails", "3.0.0.beta3"
|
|||||||
gem "liquid"
|
gem "liquid"
|
||||||
gem "bson_ext", '0.20.1'
|
gem "bson_ext", '0.20.1'
|
||||||
gem "mongo_ext"
|
gem "mongo_ext"
|
||||||
gem "mongoid", ">= 2.0.0.beta2"
|
gem "mongoid", ">= 2.0.0.beta4"
|
||||||
|
gem "mongoid_acts_as_tree", :git => 'git://github.com/evansagge/mongoid_acts_as_tree.git'
|
||||||
gem "warden"
|
gem "warden"
|
||||||
gem "devise", ">= 1.1.rc0"
|
gem "devise", ">= 1.1.rc0"
|
||||||
gem "haml", '>= 3.0.0.beta.2', :git => 'git://github.com/nex3/haml.git'
|
gem "haml", '>= 3.0.0.beta.2', :git => 'git://github.com/nex3/haml.git'
|
||||||
@ -28,5 +29,9 @@ group :test do
|
|||||||
gem 'rspec-rails', '>= 2.0.0.beta.5'
|
gem 'rspec-rails', '>= 2.0.0.beta.5'
|
||||||
gem 'factory_girl', :git => 'git://github.com/thoughtbot/factory_girl.git', :branch => 'rails3'
|
gem 'factory_girl', :git => 'git://github.com/thoughtbot/factory_girl.git', :branch => 'rails3'
|
||||||
gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git'
|
gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git'
|
||||||
gem 'cucumber-rails', :git => 'git://github.com/aslakhellesoy/cucumber-rails.git'
|
gem 'cucumber', '0.7.2'
|
||||||
|
gem 'cucumber-rails'
|
||||||
|
gem 'spork'
|
||||||
|
gem 'launchy'
|
||||||
|
gem 'mocha', :git => 'git://github.com/floehopper/mocha.git'
|
||||||
end
|
end
|
@ -5,7 +5,7 @@ class Admin::LayoutsController < Admin::BaseController
|
|||||||
def index
|
def index
|
||||||
@layouts = current_site.layouts
|
@layouts = current_site.layouts
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@layout = current_site.layouts.build
|
@layout = current_site.layouts.build
|
||||||
end
|
end
|
||||||
|
10
app/controllers/admin/page_parts_controller.rb
Normal file
10
app/controllers/admin/page_parts_controller.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class Admin::PagePartsController < Admin::BaseController
|
||||||
|
|
||||||
|
layout nil
|
||||||
|
|
||||||
|
def index
|
||||||
|
parts = current_site.layouts.find(params[:layout_id]).parts
|
||||||
|
render :json => { :parts => parts }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -8,10 +8,11 @@ class Admin::PagesController < Admin::BaseController
|
|||||||
|
|
||||||
def new
|
def new
|
||||||
@page = current_site.pages.build
|
@page = current_site.pages.build
|
||||||
|
@page.parts << PagePart.build_body_part
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@page = current_site.pages.find(params[:id])
|
@page = current_site.pages.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -6,4 +6,10 @@ class Admin::SessionsController < Devise::SessionsController
|
|||||||
|
|
||||||
before_filter :require_site
|
before_filter :require_site
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def after_sign_in_path_for(resource)
|
||||||
|
admin_pages_url
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -3,7 +3,6 @@ module Admin::PagesHelper
|
|||||||
def parent_pages_options
|
def parent_pages_options
|
||||||
roots = current_site.pages.roots.where(:slug.ne => '404').and(:_id.ne => @page.id)
|
roots = current_site.pages.roots.where(:slug.ne => '404').and(:_id.ne => @page.id)
|
||||||
|
|
||||||
puts roots.to_a.inspect
|
|
||||||
returning [] do |list|
|
returning [] do |list|
|
||||||
roots.each do |page|
|
roots.each do |page|
|
||||||
list = add_children_to_options(page, list)
|
list = add_children_to_options(page, list)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
class Layout < LiquidTemplate
|
class Layout < LiquidTemplate
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
|
has_many_related :pages
|
||||||
embeds_many :parts, :class_name => 'PagePart'
|
embeds_many :parts, :class_name => 'PagePart'
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
before_save :build_parts_from_value
|
before_save :build_parts_from_value
|
||||||
|
after_save :update_parts_in_pages
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_format_of :value, :with => Locomotive::Regexps::CONTENT_FOR_LAYOUT, :message => :missing_content_for_layout
|
validates_format_of :value, :with => Locomotive::Regexps::CONTENT_FOR_LAYOUT, :message => :missing_content_for_layout
|
||||||
@ -15,23 +17,31 @@ class Layout < LiquidTemplate
|
|||||||
|
|
||||||
def build_parts_from_value
|
def build_parts_from_value
|
||||||
if self.value_changed? || self.new_record?
|
if self.value_changed? || self.new_record?
|
||||||
self.parts = []
|
self.parts.clear
|
||||||
|
|
||||||
(groups = self.value.scan(Locomotive::Regexps::CONTENT_FOR)).each do |part|
|
body = nil
|
||||||
|
|
||||||
|
self.value.scan(Locomotive::Regexps::CONTENT_FOR).each do |part|
|
||||||
part[1].strip!
|
part[1].strip!
|
||||||
part[1] = nil if part[1].empty?
|
part[1] = nil if part[1].empty?
|
||||||
|
|
||||||
slug = part[0].strip.downcase
|
slug = part[0].strip.downcase
|
||||||
|
|
||||||
name = (if slug == 'layout'
|
if slug == 'layout'
|
||||||
I18n.t('admin.shared.attributes.body')
|
body = PagePart.new :slug => slug, :name => I18n.t('admin.shared.attributes.body')
|
||||||
else
|
else
|
||||||
(part[1] || slug).gsub("\"", '')
|
self.parts.build :slug => slug, :name => (part[1] || slug).gsub("\"", '')
|
||||||
end)
|
end
|
||||||
|
|
||||||
self.parts.build :slug => slug, :name => name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.parts.insert(0, body) if body
|
||||||
|
|
||||||
|
@_update_pages = true if self.value_changed?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_parts_in_pages
|
||||||
|
self.pages.each { |p| p.send(:update_parts!, self.parts) } if @_update_pages
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -13,6 +13,7 @@ class Page
|
|||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
belongs_to_related :site
|
belongs_to_related :site
|
||||||
|
belongs_to_related :layout
|
||||||
embeds_many :parts, :class_name => 'PagePart'
|
embeds_many :parts, :class_name => 'PagePart'
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
@ -22,7 +23,7 @@ class Page
|
|||||||
before_save :change_parent
|
before_save :change_parent
|
||||||
before_create { |p| p.fix_position(false) }
|
before_create { |p| p.fix_position(false) }
|
||||||
before_create :add_to_list_bottom
|
before_create :add_to_list_bottom
|
||||||
before_create :add_body_part
|
# before_create :add_body_part
|
||||||
before_destroy :remove_from_list
|
before_destroy :remove_from_list
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
@ -34,6 +35,7 @@ class Page
|
|||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
acts_as_tree :order => ['position', 'asc']
|
acts_as_tree :order => ['position', 'asc']
|
||||||
|
# accepts_nested_attributes_for :parts, :allow_destroy => true
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
@ -45,8 +47,12 @@ class Page
|
|||||||
self.slug == '404' && self.depth.to_i == 0
|
self.slug == '404' && self.depth.to_i == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parts_attributes=(attributes)
|
||||||
|
self.update_parts(attributes.values.map { |attrs| PagePart.new(attrs) })
|
||||||
|
end
|
||||||
|
|
||||||
def add_body_part
|
def add_body_part
|
||||||
self.parts.build :name => 'body', :value => '---body here---'
|
self.parts.build :name => 'body', :slug => 'layout', :value => '---body here---'
|
||||||
end
|
end
|
||||||
|
|
||||||
def parent=(owner) # missing in acts_as_tree
|
def parent=(owner) # missing in acts_as_tree
|
||||||
@ -76,11 +82,35 @@ class Page
|
|||||||
|
|
||||||
def ancestors
|
def ancestors
|
||||||
return [] if root?
|
return [] if root?
|
||||||
self.class.find(self.path.clone << nil) # bug in mongoid (it does not handle array with on element)
|
self.class.find(self.path.clone << nil) # bug in mongoid (it does not handle array with one element)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def update_parts(parts)
|
||||||
|
performed = []
|
||||||
|
|
||||||
|
# add / update
|
||||||
|
parts.each do |part|
|
||||||
|
if (existing = self.parts.detect { |p| p.id == part.id || p.slug == part.slug })
|
||||||
|
existing.attributes = part.attributes.delete_if { |k, v| %w{_id slug}.include?(k) }
|
||||||
|
else
|
||||||
|
self.parts << (existing = part)
|
||||||
|
end
|
||||||
|
performed << existing unless existing.disabled?
|
||||||
|
end
|
||||||
|
|
||||||
|
# disable missing parts
|
||||||
|
(self.parts.map(&:slug) - performed.map(&:slug)).each do |slug|
|
||||||
|
self.parts.detect { |p| p.slug == slug }.disabled = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_parts!(new_parts)
|
||||||
|
self.update_parts(new_parts)
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
|
||||||
def change_parent
|
def change_parent
|
||||||
if self.parent_id_changed?
|
if self.parent_id_changed?
|
||||||
self.fix_position(false)
|
self.fix_position(false)
|
||||||
|
@ -1,20 +1,36 @@
|
|||||||
class PagePart
|
class PagePart
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
include Mongoid::Timestamps
|
# include Mongoid::Timestamps
|
||||||
|
|
||||||
## fields ##
|
## fields ##
|
||||||
field :name, :type => String
|
field :name, :type => String
|
||||||
field :slug, :type => String
|
field :slug, :type => String
|
||||||
field :value, :type => String
|
field :value, :type => String
|
||||||
field :disabled, :type => Boolean, :default => false
|
field :disabled, :type => Boolean, :default => false
|
||||||
|
field :value, :type => String
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
embedded_in :page, :inverse_of => :parts
|
embedded_in :page, :inverse_of => :parts
|
||||||
|
|
||||||
|
# attr_accessor :_delete
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
before_validate { |p| p.slug ||= p.name.slugify if p.name.present? }
|
# before_validate { |p| p.slug ||= p.name.slugify if p.name.present? }
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :name, :slug
|
validates_presence_of :name, :slug
|
||||||
|
|
||||||
|
## named scopes ##
|
||||||
|
named_scope :enabled, where(:disabled => false)
|
||||||
|
|
||||||
|
## methods ##
|
||||||
|
|
||||||
|
# def _delete=(value)
|
||||||
|
# puts "set _delete #{value.inspect}"
|
||||||
|
# self.attributes[:_destroy] = true if %w(t 1 true).include?(value)
|
||||||
|
# end
|
||||||
|
|
||||||
|
def self.build_body_part
|
||||||
|
self.new(:name => I18n.t('admin.shared.attributes.body'), :slug => 'layout')
|
||||||
|
end
|
||||||
end
|
end
|
@ -1,7 +1,13 @@
|
|||||||
|
- content_for :head do
|
||||||
|
= javascript_include_tag 'admin/plugins/codemirror/codemirror', 'admin/plugins/wslide', 'admin/pages', 'admin/page_parts'
|
||||||
|
= stylesheet_link_tag 'admin/page_parts'
|
||||||
|
|
||||||
= f.foldable_inputs :name => :information do
|
= f.foldable_inputs :name => :information do
|
||||||
|
|
||||||
= f.input :title
|
= f.input :title
|
||||||
|
|
||||||
|
= f.input :layout_id, :as => :select, :collection => current_site.layouts.all, :input_html => { :data_url => admin_layout_page_parts_url('_id_to_replace_') }
|
||||||
|
|
||||||
- if not @page.index? and not @page.not_found?
|
- if not @page.index? and not @page.not_found?
|
||||||
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false
|
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false
|
||||||
|
|
||||||
@ -15,57 +21,17 @@
|
|||||||
= f.input :keywords
|
= f.input :keywords
|
||||||
= f.input :description
|
= f.input :description
|
||||||
|
|
||||||
|
#page-parts
|
||||||
|
.nav
|
||||||
|
- @page.parts.each_with_index do |part, index|
|
||||||
|
= link_to content_tag(:span, part.name), "#parts-#{index + 1}", :id => "control-part-#{part.slug}", :class => "part-#{index} #{'on' if index == 0}", :style => "#{'display: none' if part.disabled?}"
|
||||||
|
.clear
|
||||||
|
|
||||||
|
.wrapper
|
||||||
|
%ul{ :id => "parts" }
|
||||||
/ <% f.foldable_inputs :name => :information do %>
|
= f.fields_for :parts do |g|
|
||||||
/ <%= f.input :title, :required => false %>
|
%li{ :style => "#{'display: none' if g.object.disabled?}" }
|
||||||
/
|
%code= g.text_area :value
|
||||||
/ <% f.custom_input :path, :css => 'path' do %>
|
= g.hidden_field :name
|
||||||
/ /<%= f.text_field :path %>.html
|
= g.hidden_field :slug
|
||||||
/ <% end %>
|
= g.hidden_field :disabled, :class => 'disabled'
|
||||||
/
|
|
||||||
/ <% 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? %>
|
|
||||||
/ <div id="page-parts">
|
|
||||||
/ <div class="jcarousel-control">
|
|
||||||
/ <% if body_part? %>
|
|
||||||
/ <a class="part-0 on"><%= t('admin.pages.form.body') %></a><span></span><% end -%><% @page.parts.active.each_with_index do |p, index| -%><a class="part-<%= index + (body_part? ? 1 : 0) %> <%= 'on' if !body_part? && index == 0 %>"><%= p.slug.humanize %></a><span></span>
|
|
||||||
/ <% end %>
|
|
||||||
/ </div>
|
|
||||||
/
|
|
||||||
/ <div class="wrapper">
|
|
||||||
/ <ul>
|
|
||||||
/ <% if body_part? %>
|
|
||||||
/ <li><code><%= f.text_area :body %></code></li>
|
|
||||||
/ <% end %>
|
|
||||||
/
|
|
||||||
/ <% f.fields_for :parts do |g| %>
|
|
||||||
/ <% unless g.object.disabled? %>
|
|
||||||
/ <li><code><%= g.text_area :body, :class => 'big' %></code></li>
|
|
||||||
/ <% end %>
|
|
||||||
/ <% end %>
|
|
||||||
/ </ul>
|
|
||||||
/ </div>
|
|
||||||
/ </div>
|
|
||||||
/
|
|
||||||
/ <% 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 %>
|
|
@ -1,8 +1,5 @@
|
|||||||
- title link_to(@page.title.blank? ? @page.title_was : @page.title, '#', :rel => 'page_title', :title => t('.ask_for_title'), :class => 'editable')
|
- title link_to(@page.title.blank? ? @page.title_was : @page.title, '#', :rel => 'page_title', :title => t('.ask_for_title'), :class => 'editable')
|
||||||
|
|
||||||
- content_for :head do
|
|
||||||
= javascript_include_tag 'admin/pages'
|
|
||||||
|
|
||||||
- content_for :submenu do
|
- content_for :submenu do
|
||||||
= render 'admin/shared/menu/contents'
|
= render 'admin/shared/menu/contents'
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
- title t('.title')
|
- title t('.title')
|
||||||
|
|
||||||
- content_for :head do
|
|
||||||
= javascript_include_tag 'admin/pages'
|
|
||||||
|
|
||||||
- content_for :submenu do
|
- content_for :submenu do
|
||||||
= render 'admin/shared/menu/contents'
|
= render 'admin/shared/menu/contents'
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<%
|
<%
|
||||||
rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
|
rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
|
||||||
rerun_opts = rerun.to_s.strip.empty? ? "--format progress features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
|
rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
|
||||||
std_opts = "#{rerun_opts} --format rerun --out rerun.txt --strict --tags ~@wip"
|
std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} --strict --tags ~@wip"
|
||||||
%>
|
%>
|
||||||
default: <%= std_opts %>
|
default: <%= std_opts %> features
|
||||||
wip: --tags @wip:3 --wip features
|
wip: --tags @wip:3 --wip features
|
||||||
|
rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
|
||||||
|
@ -5,7 +5,7 @@ development:
|
|||||||
<<: *defaults
|
<<: *defaults
|
||||||
database: locomotive_dev
|
database: locomotive_dev
|
||||||
|
|
||||||
test:
|
test: &test
|
||||||
<<: *defaults
|
<<: *defaults
|
||||||
database: locomotive_test
|
database: locomotive_test
|
||||||
|
|
||||||
@ -15,3 +15,6 @@ production:
|
|||||||
username: user
|
username: user
|
||||||
password: pass
|
password: pass
|
||||||
database: fanboy
|
database: fanboy
|
||||||
|
|
||||||
|
cucumber:
|
||||||
|
<<: *test
|
@ -14,8 +14,10 @@ end
|
|||||||
|
|
||||||
## various patches
|
## various patches
|
||||||
|
|
||||||
# Enabling scope in validates_uniqueness_of validation
|
|
||||||
module Mongoid #:nodoc:
|
module Mongoid #:nodoc:
|
||||||
|
|
||||||
|
# Enabling scope in validates_uniqueness_of validation
|
||||||
module Validations #:nodoc:
|
module Validations #:nodoc:
|
||||||
class UniquenessValidator < ActiveModel::EachValidator
|
class UniquenessValidator < ActiveModel::EachValidator
|
||||||
def validate_each(document, attribute, value, scope = nil)
|
def validate_each(document, attribute, value, scope = nil)
|
||||||
@ -26,4 +28,14 @@ module Mongoid #:nodoc:
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
# FIX BUG #71 http://github.com/durran/mongoid/commit/47a97094b32448aa09965c854a24c78803c7f42e
|
||||||
|
module Associations
|
||||||
|
module InstanceMethods
|
||||||
|
def update_embedded(name)
|
||||||
|
association = send(name)
|
||||||
|
association.to_a.each { |doc| doc.save if doc.changed? || doc.new_record? } unless association.blank?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -20,7 +20,7 @@ en:
|
|||||||
|
|
||||||
messages:
|
messages:
|
||||||
confirm: Are you sure ?
|
confirm: Are you sure ?
|
||||||
|
|
||||||
shared:
|
shared:
|
||||||
header:
|
header:
|
||||||
welcome: Welcome, {{name}}
|
welcome: Welcome, {{name}}
|
||||||
@ -48,6 +48,9 @@ en:
|
|||||||
title: Listing pages
|
title: Listing pages
|
||||||
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||||
new: new page
|
new: new page
|
||||||
|
messages:
|
||||||
|
successful_create: "Page was successfully created."
|
||||||
|
successful_update: "Page was successfully updated."
|
||||||
|
|
||||||
layouts:
|
layouts:
|
||||||
index:
|
index:
|
||||||
|
@ -22,7 +22,9 @@ Locomotive::Application.routes.draw do |map|
|
|||||||
get :get_path, :on => :collection
|
get :get_path, :on => :collection
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :layouts
|
resources :layouts do
|
||||||
|
resources :page_parts, :only => :index
|
||||||
|
end
|
||||||
resources :snippets
|
resources :snippets
|
||||||
|
|
||||||
# get 'login' => 'sessions#new', :as => :new_account_session
|
# get 'login' => 'sessions#new', :as => :new_account_session
|
||||||
|
13
doc/TODO
13
doc/TODO
@ -15,14 +15,19 @@ x store node closed or open in cookies
|
|||||||
x snippets section
|
x snippets section
|
||||||
x menu items have to be translated
|
x menu items have to be translated
|
||||||
x layout needs at least content_for_layout
|
x layout needs at least content_for_layout
|
||||||
- validates_uniqueness_of :slug, :scope => :id
|
x parts js/css:
|
||||||
- page parts
|
x codemirror
|
||||||
- can not delete index + 404 pages
|
x change bg (separator)
|
||||||
|
x when a tab is selected, if we change layout, we should move to the first visible one
|
||||||
|
x page parts
|
||||||
|
x layout part should be always in first
|
||||||
|
x pages section (CRUD)
|
||||||
- slug unique within a folder
|
- slug unique within a folder
|
||||||
- pages section (CRUD)
|
- validates_uniqueness_of :slug, :scope => :id
|
||||||
- refactoring page.rb => create module pagetree
|
- refactoring page.rb => create module pagetree
|
||||||
- my account section (part of settings)
|
- my account section (part of settings)
|
||||||
- layouts section
|
- layouts section
|
||||||
- create 404 + index pages once a site is created
|
- create 404 + index pages once a site is created
|
||||||
|
- can not delete index + 404 pages
|
||||||
- refactoring admin crud (pages + layouts + snippets)
|
- refactoring admin crud (pages + layouts + snippets)
|
||||||
- remove all pages, snippets, ...etc when destroying a website
|
- remove all pages, snippets, ...etc when destroying a website
|
19
features/admin/login.feature
Normal file
19
features/admin/login.feature
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@site_up
|
||||||
|
Feature: Login
|
||||||
|
In order to access locomotive admin panel
|
||||||
|
As an administrator
|
||||||
|
I want to log in
|
||||||
|
|
||||||
|
Scenario: Successful authentication
|
||||||
|
When I go to login
|
||||||
|
And I fill in "account_email" with "admin@locomotiveapp.org"
|
||||||
|
And I fill in "account_password" with "easyone"
|
||||||
|
And I press "Log in"
|
||||||
|
Then I should see "Listing pages"
|
||||||
|
|
||||||
|
Scenario: Failed authentication
|
||||||
|
When I go to login
|
||||||
|
And I fill in "account_email" with "admin@locomotiveapp.org"
|
||||||
|
And I fill in "account_password" with ""
|
||||||
|
And I press "Log in"
|
||||||
|
Then I should not see "Listing pages"
|
31
features/admin/pages.feature
Normal file
31
features/admin/pages.feature
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@site_up
|
||||||
|
@authenticated
|
||||||
|
Feature: Manage Skills
|
||||||
|
In order to manage pages
|
||||||
|
As an administrator
|
||||||
|
I want to add/edit/delete pages of my site
|
||||||
|
|
||||||
|
Scenario: Pages list is not accessible for non authenticated accounts
|
||||||
|
Given I am not authenticated
|
||||||
|
When I go to pages
|
||||||
|
Then I should see "Login"
|
||||||
|
|
||||||
|
Scenario: Creating a valid page
|
||||||
|
When I go to pages
|
||||||
|
And I follow "new page"
|
||||||
|
And I fill in "Title" with "Test"
|
||||||
|
And I fill in "Slug" with "test"
|
||||||
|
And I select "Home page" from "Parent"
|
||||||
|
And I fill in "page_parts_attributes_0_value" with "Lorem ipsum...."
|
||||||
|
And I press "Create"
|
||||||
|
Then I should see "Page was successfully created."
|
||||||
|
And I should have "Lorem ipsum...." in the test page layout
|
||||||
|
|
||||||
|
Scenario: Updating a valid page
|
||||||
|
When I go to pages
|
||||||
|
And I follow "Home page"
|
||||||
|
And I fill in "Title" with "Home page !"
|
||||||
|
And I fill in "page_parts_attributes_0_value" with "My new content is here"
|
||||||
|
And I press "Update"
|
||||||
|
Then I should see "Page was successfully updated."
|
||||||
|
And I should have "My new content is here" in the index page layout
|
63
features/step_definitions/admin_steps.rb
Normal file
63
features/step_definitions/admin_steps.rb
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
Before('@site_up') do
|
||||||
|
create_site_and_admin_account
|
||||||
|
create_layout_samples
|
||||||
|
create_index_page
|
||||||
|
end
|
||||||
|
|
||||||
|
Before('@authenticated') do
|
||||||
|
Given %{I am an authenticated user}
|
||||||
|
end
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Given /^I am not authenticated$/ do
|
||||||
|
visit('/admin/logout')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
Given /^I am an authenticated user$/ do
|
||||||
|
Given %{I go to login}
|
||||||
|
And %{I fill in "account_email" with "admin@locomotiveapp.org"}
|
||||||
|
And %{I fill in "account_password" with "easyone"}
|
||||||
|
And %{I press "Log in"}
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I am redirected to "([^\"]*)"$/ do |url|
|
||||||
|
assert [301, 302].include?(@integration_session.status), "Expected status to be 301 or 302, got #{@integration_session.status}"
|
||||||
|
location = @integration_session.headers["Location"]
|
||||||
|
assert_equal url, location
|
||||||
|
visit location
|
||||||
|
end
|
||||||
|
|
||||||
|
### Pages
|
||||||
|
|
||||||
|
|
||||||
|
Then /^I should have "(.*)" in the (.*) page (.*)$/ do |content, page_slug, slug|
|
||||||
|
page = @site.pages.where(:slug => page_slug).first
|
||||||
|
part = page.parts.where(:slug => slug).first
|
||||||
|
part.should_not be_nil
|
||||||
|
part.value.should == content
|
||||||
|
end
|
||||||
|
|
||||||
|
## Common
|
||||||
|
|
||||||
|
def create_site_and_admin_account
|
||||||
|
@site = Factory(:site, :name => 'Locomotive test website', :subdomain => 'test')
|
||||||
|
@admin = Factory(:account, { :name => 'Admin', :email => 'admin@locomotiveapp.org' })
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_layout_samples
|
||||||
|
Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
|
||||||
|
<head>
|
||||||
|
<title>My website</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="main">\{\{ content_for_layout \}\}</div>
|
||||||
|
</body>
|
||||||
|
</html>})
|
||||||
|
Factory(:layout, :site => @site)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_index_page
|
||||||
|
Factory(:page, :site => @site, :layout => @site.layouts.last, :parts => [PagePart.new(:slug => 'layout', :name => 'Body', :value => 'Hello world')])
|
||||||
|
end
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
|
|
||||||
require 'uri'
|
require 'uri'
|
||||||
|
require 'cgi'
|
||||||
require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
|
require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
|
||||||
|
|
||||||
module WithinHelpers
|
module WithinHelpers
|
||||||
@ -147,7 +148,8 @@ end
|
|||||||
|
|
||||||
Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should contain "([^\"]*)"$/ do |field, selector, value|
|
Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should contain "([^\"]*)"$/ do |field, selector, value|
|
||||||
with_scope(selector) do
|
with_scope(selector) do
|
||||||
field_value = find_field(field).value
|
field = find_field(field)
|
||||||
|
field_value = (field.tag_name == 'textarea') ? field.text : field.value
|
||||||
if field_value.respond_to? :should
|
if field_value.respond_to? :should
|
||||||
field_value.should =~ /#{value}/
|
field_value.should =~ /#{value}/
|
||||||
else
|
else
|
||||||
@ -158,11 +160,12 @@ end
|
|||||||
|
|
||||||
Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should not contain "([^\"]*)"$/ do |field, selector, value|
|
Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should not contain "([^\"]*)"$/ do |field, selector, value|
|
||||||
with_scope(selector) do
|
with_scope(selector) do
|
||||||
field_value = find_field(field).value
|
field = find_field(field)
|
||||||
|
field_value = (field.tag_name == 'textarea') ? field.text : field.value
|
||||||
if field_value.respond_to? :should_not
|
if field_value.respond_to? :should_not
|
||||||
field_value.should_not =~ /#{value}/
|
field_value.should_not =~ /#{value}/
|
||||||
else
|
else
|
||||||
assert_no_match(/#{value}/, field_value.value)
|
assert_no_match(/#{value}/, field_value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -199,9 +202,11 @@ Then /^(?:|I )should be on (.+)$/ do |page_name|
|
|||||||
end
|
end
|
||||||
|
|
||||||
Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
|
Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
|
||||||
actual_params = CGI.parse(URI.parse(current_url).query)
|
query = URI.parse(current_url).query
|
||||||
expected_params = Hash[expected_pairs.rows_hash.map{|k,v| [k,[v]]}]
|
actual_params = query ? CGI.parse(query) : {}
|
||||||
|
expected_params = {}
|
||||||
|
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
|
||||||
|
|
||||||
if actual_params.respond_to? :should
|
if actual_params.respond_to? :should
|
||||||
actual_params.should == expected_params
|
actual_params.should == expected_params
|
||||||
else
|
else
|
||||||
|
@ -10,7 +10,6 @@ require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
|
|||||||
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
|
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
|
||||||
require 'cucumber/rails/rspec'
|
require 'cucumber/rails/rspec'
|
||||||
require 'cucumber/rails/world'
|
require 'cucumber/rails/world'
|
||||||
require 'cucumber/rails/active_record'
|
|
||||||
require 'cucumber/web/tableish'
|
require 'cucumber/web/tableish'
|
||||||
|
|
||||||
require 'capybara/rails'
|
require 'capybara/rails'
|
||||||
@ -34,26 +33,27 @@ Capybara.default_selector = :css
|
|||||||
# of your scenarios, as this makes it hard to discover errors in your application.
|
# of your scenarios, as this makes it hard to discover errors in your application.
|
||||||
ActionController::Base.allow_rescue = false
|
ActionController::Base.allow_rescue = false
|
||||||
|
|
||||||
# If you set this to true, each scenario will run in a database transaction.
|
require 'factory_girl'
|
||||||
# You can still turn off transactions on a per-scenario basis, simply tagging
|
require 'spec/factories'
|
||||||
# a feature or scenario with the @no-txn tag. If you are using Capybara,
|
|
||||||
# tagging with @culerity or @javascript will also turn transactions off.
|
|
||||||
#
|
|
||||||
# If you set this to false, transactions will be off for all scenarios,
|
|
||||||
# regardless of whether you use @no-txn or not.
|
|
||||||
#
|
|
||||||
# Beware that turning transactions off will leave data in your database
|
|
||||||
# after each scenario, which can lead to hard-to-debug failures in
|
|
||||||
# subsequent scenarios. If you do this, we recommend you create a Before
|
|
||||||
# block that will explicitly put your database in a known state.
|
|
||||||
Cucumber::Rails::World.use_transactional_fixtures = true
|
|
||||||
|
|
||||||
# How to clean your database when transactions are turned off. See
|
Before do
|
||||||
# http://github.com/bmabey/database_cleaner for more info.
|
Mongoid.master.collections.each(&:drop)
|
||||||
if defined?(ActiveRecord::Base)
|
|
||||||
begin
|
|
||||||
require 'database_cleaner'
|
|
||||||
DatabaseCleaner.strategy = :truncation
|
|
||||||
rescue LoadError => ignore_if_database_cleaner_not_present
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Locomotive.configure do |config|
|
||||||
|
config.default_domain = 'example.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
# class ActionController::Integration::Session
|
||||||
|
# def reset_with_test_subdomain!
|
||||||
|
# self.reset_without_test_subdomain!
|
||||||
|
# self.host = "test.example.com"
|
||||||
|
# end
|
||||||
|
# alias_method_chain :reset!, :test_subdomain
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# class ActionDispatch::Integration::Session
|
||||||
|
# DEFAULT_HOST = 'test.example.com'
|
||||||
|
# end
|
||||||
|
|
||||||
|
Capybara.default_host = 'test.example.com'
|
@ -7,10 +7,16 @@ module NavigationHelpers
|
|||||||
#
|
#
|
||||||
def path_to(page_name)
|
def path_to(page_name)
|
||||||
case page_name
|
case page_name
|
||||||
|
|
||||||
when /the home\s?page/
|
when /the home\s?page/
|
||||||
'/'
|
'/'
|
||||||
|
when /login/
|
||||||
|
new_account_session_path
|
||||||
|
when /logout/
|
||||||
|
destroy_account_session_path
|
||||||
|
when /pages/
|
||||||
|
admin_pages_path
|
||||||
|
|
||||||
# Add more mappings here.
|
# Add more mappings here.
|
||||||
# Here is an example that pulls values out of the Regexp:
|
# Here is an example that pulls values out of the Regexp:
|
||||||
#
|
#
|
||||||
@ -18,8 +24,14 @@ module NavigationHelpers
|
|||||||
# user_profile_path(User.find_by_login($1))
|
# user_profile_path(User.find_by_login($1))
|
||||||
|
|
||||||
else
|
else
|
||||||
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
begin
|
||||||
"Now, go and add a mapping in #{__FILE__}"
|
page_name =~ /the (.*) page/
|
||||||
|
path_components = $1.split(/\s+/)
|
||||||
|
self.send(path_components.push('path').join('_').to_sym)
|
||||||
|
rescue Object => e
|
||||||
|
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
||||||
|
"Now, go and add a mapping in #{__FILE__}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -14,18 +14,24 @@ begin
|
|||||||
require 'cucumber/rake/task'
|
require 'cucumber/rake/task'
|
||||||
|
|
||||||
namespace :cucumber do
|
namespace :cucumber do
|
||||||
Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
|
Cucumber::Rake::Task.new(:ok, 'Run features that should pass') do |t|
|
||||||
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
|
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
|
||||||
t.fork = true # You may get faster startup if you set this to false
|
t.fork = true # You may get faster startup if you set this to false
|
||||||
t.profile = 'default'
|
t.profile = 'default'
|
||||||
end
|
end
|
||||||
|
|
||||||
Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
|
Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
|
||||||
t.binary = vendored_cucumber_bin
|
t.binary = vendored_cucumber_bin
|
||||||
t.fork = true # You may get faster startup if you set this to false
|
t.fork = true # You may get faster startup if you set this to false
|
||||||
t.profile = 'wip'
|
t.profile = 'wip'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Cucumber::Rake::Task.new(:rerun, 'Record failing features and run only them if any exist') do |t|
|
||||||
|
t.binary = vendored_cucumber_bin
|
||||||
|
t.fork = true # You may get faster startup if you set this to false
|
||||||
|
t.profile = 'rerun'
|
||||||
|
end
|
||||||
|
|
||||||
desc 'Run all features'
|
desc 'Run all features'
|
||||||
task :all => [:ok, :wip]
|
task :all => [:ok, :wip]
|
||||||
end
|
end
|
||||||
|
BIN
public/images/admin/form/header-left-on.png
Normal file
BIN
public/images/admin/form/header-left-on.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 235 B |
BIN
public/images/admin/form/header-right-on.png
Normal file
BIN
public/images/admin/form/header-right-on.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 311 B |
@ -24,7 +24,7 @@ var addCodeMirrorEditor = function(type, el, parser) {
|
|||||||
if (type == 'liquid') type = 'xml';
|
if (type == 'liquid') type = 'xml';
|
||||||
|
|
||||||
var editor = CodeMirror.fromTextArea(el.attr('id'), {
|
var editor = CodeMirror.fromTextArea(el.attr('id'), {
|
||||||
height: "330px",
|
height: "400px",
|
||||||
parserfile: parserfile,
|
parserfile: parserfile,
|
||||||
stylesheet: ["/stylesheets/admin/plugins/codemirror/" + type + "colors.css", "/stylesheets/admin/plugins/codemirror/liquidcolors.css"],
|
stylesheet: ["/stylesheets/admin/plugins/codemirror/" + type + "colors.css", "/stylesheets/admin/plugins/codemirror/liquidcolors.css"],
|
||||||
path: "/javascripts/admin/plugins/codemirror/",
|
path: "/javascripts/admin/plugins/codemirror/",
|
||||||
|
83
public/javascripts/admin/page_parts.js
Normal file
83
public/javascripts/admin/page_parts.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// slider
|
||||||
|
var resetSlider = function() {
|
||||||
|
$('#page-parts .wrapper ul').wslide({
|
||||||
|
width: 880,
|
||||||
|
height: 400,
|
||||||
|
autolink: false,
|
||||||
|
duration: 300,
|
||||||
|
horiz: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSlider();
|
||||||
|
|
||||||
|
// codemirror
|
||||||
|
$('#parts code textarea').each(function() { addCodeMirrorEditor('liquid', $(this)); });
|
||||||
|
|
||||||
|
var refreshParts = function(parts) {
|
||||||
|
console.log('refreshParts');
|
||||||
|
$('#page-parts .nav a').removeClass('enabled');
|
||||||
|
|
||||||
|
$(parts).each(function() {
|
||||||
|
console.log("iterating..." + this.slug);
|
||||||
|
var control = $('#control-part-' + this.slug);
|
||||||
|
|
||||||
|
// adding missing part
|
||||||
|
if (control.size() == 0) {
|
||||||
|
console.log('adding part');
|
||||||
|
var nbParts = $('#page-parts .nav a').size();
|
||||||
|
$('#page-parts .nav .clear').before('<a id="control-part-' + this.slug + '" class="enabled part-' + nbParts + '" href="#parts-' + (nbParts + 1) + '"><span>' + this.name + '</span></a>');
|
||||||
|
|
||||||
|
var textareaInput = '<textarea rows="20" name="page[parts_attributes][' + nbParts + '][value]" id="page_parts_attributes_' + nbParts + '_value"/>';
|
||||||
|
var hiddenInputs = '<input type="hidden" name="page[parts_attributes][' + nbParts + '][name]" value="' + this.name + '" />'
|
||||||
|
hiddenInputs += '<input type="hidden" name="page[parts_attributes][' + nbParts + '][slug]" value="' + this.slug + '" />'
|
||||||
|
$('#page-parts .wrapper ul').append('<li id="part-' + nbParts + '" class="new"><code>' + textareaInput + '</code>' + hiddenInputs + '</li>');
|
||||||
|
|
||||||
|
resetSlider();
|
||||||
|
$('#parts li:last code textarea').each(function() { addCodeMirrorEditor('liquid', $(this)); });
|
||||||
|
} else {
|
||||||
|
var index = parseInt(control.attr('class').match(/part-(.+)/)[1]) + 1;
|
||||||
|
var wrapper = $('#parts-' + index);
|
||||||
|
|
||||||
|
// updating part
|
||||||
|
control.html('<span>' + this.name + '</span>').addClass('enabled').show();
|
||||||
|
wrapper.find('input.disabled').val('false');
|
||||||
|
wrapper.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// removing or hiding parts
|
||||||
|
$('#page-parts .nav a:not(.enabled)').each(function() {
|
||||||
|
var index = parseInt($(this).attr('class').match(/part-(.+)/)[1]) + 1;
|
||||||
|
var wrapper = $('#parts-' + index);
|
||||||
|
if (wrapper.hasClass('new')) {
|
||||||
|
$(this).remove(); wrapper.remove();
|
||||||
|
} else {
|
||||||
|
wrapper.find('input.disabled').val('true');
|
||||||
|
$(this).hide(); wrapper.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// go to the first one if we hid the selected wrapper
|
||||||
|
var selectedNav = $('#page-parts .nav a.on');
|
||||||
|
if (selectedNav.size() == 1) {
|
||||||
|
var index = parseInt(selectedNav.attr('class').match(/part-(.+)/)[1]) + 1;
|
||||||
|
if ($('#parts-' + index + ':visible').size() == 0)
|
||||||
|
$('#page-parts .nav a:first').click();
|
||||||
|
} else
|
||||||
|
$('#page-parts .nav a:first').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadPartsFromLayout = function() {
|
||||||
|
if ($('#page_layout_id').val() == '')
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var url = $('#page_layout_id').attr('data_url').replace('_id_to_replace_', $('#page_layout_id').val());
|
||||||
|
$.get(url, '', function(data) { refreshParts(data.parts) }, 'json');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#page_layout_id').change(loadPartsFromLayout);
|
||||||
|
|
||||||
|
});
|
125
public/javascripts/admin/plugins/wslide.js
Normal file
125
public/javascripts/admin/plugins/wslide.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* wSlide 0.1 - http://www.webinventif.fr/wslide-plugin/
|
||||||
|
*
|
||||||
|
* Rendez vos sites glissant !
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008 Julien Chauvin (webinventif.fr)
|
||||||
|
* Licensed under the Creative Commons License:
|
||||||
|
* http://creativecommons.org/licenses/by/3.0/
|
||||||
|
*
|
||||||
|
* Date: 2008-01-27
|
||||||
|
*/
|
||||||
|
(function($){
|
||||||
|
$.fn.wslide = function(h) {
|
||||||
|
h = jQuery.extend({
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
pos: 1,
|
||||||
|
col: 1,
|
||||||
|
effect: 'swing',
|
||||||
|
fade: false,
|
||||||
|
horiz: false,
|
||||||
|
autolink: true,
|
||||||
|
duration: 1500
|
||||||
|
}, h);
|
||||||
|
function gogogo(g){
|
||||||
|
g.each(function(i){
|
||||||
|
var a = $(this);
|
||||||
|
var uniqid = a.attr('id');
|
||||||
|
if(uniqid == undefined){
|
||||||
|
uniqid = 'wslide'+i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($(this).parent().hasClass('wslide-wrap'))
|
||||||
|
a = $(this).parent();
|
||||||
|
else {
|
||||||
|
$(this).wrap('<div class="wslide-wrap" id="'+uniqid+'-wrap"></div>');
|
||||||
|
a = $('#'+uniqid+'-wrap');
|
||||||
|
}
|
||||||
|
|
||||||
|
var b = a.find('ul li');
|
||||||
|
var effets = h.effect;
|
||||||
|
if(jQuery.easing.easeInQuad == undefined && (effets!='swing' || effets!='normal')){
|
||||||
|
effets = 'swing';
|
||||||
|
}
|
||||||
|
var typex = h.width;
|
||||||
|
var typey = h.height;
|
||||||
|
function resultante(prop){
|
||||||
|
var tempcalc = prop;
|
||||||
|
tempcalc = tempcalc.split('px');
|
||||||
|
tempcalc = tempcalc[0];
|
||||||
|
return Number(tempcalc);
|
||||||
|
}
|
||||||
|
var litypex = typex-(resultante(b.css('padding-left'))+resultante(b.css('padding-right')));
|
||||||
|
var litypey = typey-(resultante(b.css('padding-top'))+resultante(b.css('padding-bottom')));
|
||||||
|
var col = h.col;
|
||||||
|
if(h.horiz){
|
||||||
|
col = Number(b.length+1);
|
||||||
|
}
|
||||||
|
var manip = '';
|
||||||
|
var ligne = Math.ceil(Number(b.length)/col);
|
||||||
|
a.css('overflow','hidden').css('position','relative').css('text-align','left').css('height',typey+'px').css('width',typex+'px').css('margin','0').css('padding','0');
|
||||||
|
a.find('ul').css('position','absolute').css('margin','0').css('padding','0').css('width',Number((col+0)*typex)+'px').css('height',Number(ligne*typey)+'px');
|
||||||
|
b.css('display','block').css('overflow','hidden').css('float','left').css('height',litypey+'px').css('width',litypex+'px');
|
||||||
|
b.each(function (i) {
|
||||||
|
var offset = a.offset();
|
||||||
|
var thisoffset = $(this).offset();
|
||||||
|
$(this).attr('id',uniqid+'-'+Number(i+1)).attr('rel', Number(thisoffset.left-offset.left)+':'+Number(thisoffset.top-offset.top));
|
||||||
|
manip += ' <a href="#'+uniqid+'-'+Number(i+1)+'">'+Number(i+1)+'</a>';
|
||||||
|
});
|
||||||
|
|
||||||
|
if(typeof h.autolink == 'boolean'){
|
||||||
|
if(h.autolink){
|
||||||
|
a.after('<div class="wslide-menu" id="'+uniqid+'-menu">'+manip+'</div>');
|
||||||
|
}
|
||||||
|
}else if (typeof h.autolink == 'string'){
|
||||||
|
if($('#'+h.autolink).length){
|
||||||
|
$('#'+h.autolink).html(manip);
|
||||||
|
}else{
|
||||||
|
a.after('<div id="#'+h.autolink+'">'+manip+'</div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var start = '#'+uniqid+'-';
|
||||||
|
var stoccurent = "";
|
||||||
|
$('a[href*="'+start+'"]').unbind('click').click(function () {
|
||||||
|
$('a[href*="'+stoccurent+'"]').removeClass("on");
|
||||||
|
$(this).addClass("on");
|
||||||
|
var tri = $(this).attr('href');
|
||||||
|
tri=tri.split('#');
|
||||||
|
tri='#'+tri[1];
|
||||||
|
stoccurent = tri;
|
||||||
|
var decal = $(tri).attr('rel');
|
||||||
|
decal = decal.split(':');
|
||||||
|
var decal2 = decal[1];
|
||||||
|
decal2 = -decal2;
|
||||||
|
decal = decal[0];
|
||||||
|
decal = -decal;
|
||||||
|
if(h.fade){
|
||||||
|
a.find('ul').animate({ opacity: 0 }, h.duration/2, effets, function(){$(this).css('top',decal2+'px').css('left',decal+'px');$(this).animate({ opacity: 1 }, h.duration/2, effets)} );
|
||||||
|
}else{
|
||||||
|
a.find('ul').animate({ top: decal2+'px',left: decal+'px' }, h.duration, effets );
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if(h.pos <= 0){
|
||||||
|
h.pos = 1;
|
||||||
|
}
|
||||||
|
$('a[href$="'+start+h.pos+'"]').addClass("on");
|
||||||
|
var tri = $('a[href*="'+start+'"]:eq('+Number(h.pos-1)+')').attr('href');
|
||||||
|
tri=tri.split('#');
|
||||||
|
tri='#'+tri[1];
|
||||||
|
stoccurent = tri;
|
||||||
|
var decal = $(tri).attr('rel');
|
||||||
|
decal = decal.split(':');
|
||||||
|
var decal2 = decal[1];
|
||||||
|
decal2 = -decal2;
|
||||||
|
decal = decal[0];
|
||||||
|
decal = -decal;
|
||||||
|
a.find('ul').css('top',decal2+'px').css('left',decal+'px');
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
gogogo(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
})(jQuery);
|
60
public/stylesheets/admin/page_parts.css
Normal file
60
public/stylesheets/admin/page_parts.css
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#page-parts .wrapper {
|
||||||
|
background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom;
|
||||||
|
width: 880px;
|
||||||
|
padding: 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts {
|
||||||
|
background: transparent url(/images/admin/form/header.png) no-repeat 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .control {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .nav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0px 0px 0 11px;
|
||||||
|
color: #8b8d9a;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8em;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .nav a span {
|
||||||
|
display: inline-block;
|
||||||
|
height: 26px;
|
||||||
|
padding: 4px 11px 0 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .nav a:first-child { padding-left: 22px; }
|
||||||
|
#page-parts .nav a:first-child span { padding-right: 13px; }
|
||||||
|
|
||||||
|
#page-parts .nav a.on {
|
||||||
|
color: #1e1f26;
|
||||||
|
font-weight: bold;
|
||||||
|
background: transparent url(/images/admin/form/header-left-on.png) no-repeat 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .nav a.on span {
|
||||||
|
background: transparent url(/images/admin/form/header-right-on.png) no-repeat right 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .nav a:first-child.on {
|
||||||
|
background: transparent url(/images/admin/form/header-first-on.png) no-repeat 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts .wslide-wrap {
|
||||||
|
border: 1px solid #a6a8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page-parts code { display: block; background: white; height: 400px; }
|
||||||
|
#page-parts code textarea {
|
||||||
|
width: 880px;
|
||||||
|
height: 400px;
|
||||||
|
background: transparent url(../../images/admin/form/field.png) repeat-x 0 0;
|
||||||
|
border: 0px;
|
||||||
|
}
|
@ -35,11 +35,11 @@ Factory.define :layout do |l|
|
|||||||
l.name '1 main column + sidebar'
|
l.name '1 main column + sidebar'
|
||||||
l.value %{<html>
|
l.value %{<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Hello world !</title>
|
<title>My website</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main">\{\{ content_for_layout \}\}</div>
|
|
||||||
<div id="sidebar">\{\{ content_for_sidebar "Left Sidebar"\}\}</div>
|
<div id="sidebar">\{\{ content_for_sidebar "Left Sidebar"\}\}</div>
|
||||||
|
<div id="main">\{\{ content_for_layout \}\}</div>
|
||||||
</body>
|
</body>
|
||||||
</html>}
|
</html>}
|
||||||
end
|
end
|
||||||
|
@ -14,13 +14,14 @@ describe Layout do
|
|||||||
layout.errors[:value].should == ["should contain 'content_for_layout' liquid tag"]
|
layout.errors[:value].should == ["should contain 'content_for_layout' liquid tag"]
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'once created' do
|
describe 'page parts' do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@layout = Factory(:layout)
|
@layout = Factory.build(:layout)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should have 2 parts' do
|
it 'should have 2 parts' do
|
||||||
|
@layout.send(:build_parts_from_value)
|
||||||
@layout.parts.count.should == 2
|
@layout.parts.count.should == 2
|
||||||
|
|
||||||
@layout.parts.first.name.should == 'Body'
|
@layout.parts.first.name.should == 'Body'
|
||||||
@ -30,6 +31,22 @@ describe Layout do
|
|||||||
@layout.parts.last.slug.should == 'sidebar'
|
@layout.parts.last.slug.should == 'sidebar'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should not add parts to pages if layout does not change' do
|
||||||
|
@layout.stubs(:value_changed?).returns(false)
|
||||||
|
page = Factory.build(:page, :layout => @layout, :site => nil)
|
||||||
|
page.expects(:update_parts!).never
|
||||||
|
@layout.pages << page
|
||||||
|
@layout.save
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add parts to pages if layout changes' do
|
||||||
|
@layout.value = @layout.value + "..."
|
||||||
|
page = Factory.build(:page, :layout => @layout, :site => nil)
|
||||||
|
page.expects(:update_parts!)
|
||||||
|
@layout.pages << page
|
||||||
|
@layout.save
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -48,13 +48,7 @@ describe Page do
|
|||||||
Factory.build(:page, :slug => 'index', :site => nil).index?.should be_true
|
Factory.build(:page, :slug => 'index', :site => nil).index?.should be_true
|
||||||
Factory.build(:page, :slug => 'index', :depth => 1, :site => nil).index?.should be_false
|
Factory.build(:page, :slug => 'index', :depth => 1, :site => nil).index?.should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should add the body part' do
|
|
||||||
page = Factory(:page)
|
|
||||||
page.parts.should_not be_empty
|
|
||||||
page.parts.first.name.should == 'body'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'should have normalized slug' do
|
it 'should have normalized slug' do
|
||||||
page = Factory.build(:page, :slug => ' Valid ité.html ')
|
page = Factory.build(:page, :slug => ' Valid ité.html ')
|
||||||
page.valid?
|
page.valid?
|
||||||
@ -67,6 +61,120 @@ describe Page do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'accepts_nested_attributes_for used for parts' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@page = Factory.build(:page)
|
||||||
|
@page.parts.build(:name => 'Main content', :slug => 'layout')
|
||||||
|
@page.parts.build(:name => 'Left column', :slug => 'left_sidebar')
|
||||||
|
@page.parts.build(:name => 'Right column', :slug => 'right_sidebar')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add parts' do
|
||||||
|
attributes = { '0' => { 'slug' => 'footer', 'name' => 'A custom footer', 'value' => 'End of page' } }
|
||||||
|
@page.parts_attributes = attributes
|
||||||
|
@page.parts.size.should == 4
|
||||||
|
@page.parts.last.slug.should == 'footer'
|
||||||
|
@page.parts.last.disabled.should == false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update parts' do
|
||||||
|
attributes = { '0' => { 'slug' => 'layout', 'name' => 'A new name', 'value' => 'Hello world' } }
|
||||||
|
@page.parts_attributes = attributes
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts.first.slug.should == 'layout'
|
||||||
|
@page.parts.first.name.should == 'A new name'
|
||||||
|
@page.parts.first.value.should == 'Hello world'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should disable parts' do
|
||||||
|
attributes = { '0' => { 'slug' => 'left_sidebar', 'disabled' => 'true' } }
|
||||||
|
@page.parts_attributes = attributes
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts.first.disabled.should == true
|
||||||
|
@page.parts[1].disabled.should == true
|
||||||
|
@page.parts[2].disabled.should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add/update/disable parts at the same time' do
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
|
||||||
|
attributes = {
|
||||||
|
'0' => { 'slug' => 'layout', 'name' => 'Body', 'value' => 'Hello world' },
|
||||||
|
'1' => { 'slug' => 'left_sidebar', 'disabled' => 'true' },
|
||||||
|
'2' => { 'id' => @page.parts[2].id, 'value' => 'Content from right sidebar', 'disabled' => 'false' }
|
||||||
|
}
|
||||||
|
@page.parts_attributes = attributes
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
|
||||||
|
@page.parts[0].value.should == 'Hello world'
|
||||||
|
@page.parts[1].disabled.should == true
|
||||||
|
@page.parts[2].disabled.should == false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update it with success (mongoid bug #71)' do
|
||||||
|
@page.save
|
||||||
|
@page = Page.first
|
||||||
|
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts_attributes = { '0' => { 'slug' => 'header', 'name' => 'A custom header', 'value' => 'Head of page' } }
|
||||||
|
@page.parts.size.should == 4
|
||||||
|
|
||||||
|
@page.save
|
||||||
|
@page = Page.first
|
||||||
|
|
||||||
|
@page.parts.size.should == 4
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'dealing with page parts' do # DUPLICATED ?
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@page = Factory.build(:page)
|
||||||
|
@parts = [
|
||||||
|
PagePart.new(:name => 'Main content', :slug => 'layout'),
|
||||||
|
PagePart.new(:name => 'Left column', :slug => 'left_sidebar'),
|
||||||
|
PagePart.new(:name => 'Right column', :slug => 'right_sidebar')
|
||||||
|
]
|
||||||
|
@page.send(:update_parts, @parts)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add new parts from an array of parts' do
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts.shift.name.should == 'Main content'
|
||||||
|
@page.parts.shift.name.should == 'Left column'
|
||||||
|
@page.parts.shift.name.should == 'Right column'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update parts' do
|
||||||
|
@parts[1].name = 'Very left column'
|
||||||
|
@page.send(:update_parts, @parts)
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts[1].name.should == 'Very left column'
|
||||||
|
@page.parts[1].slug.should == 'left_sidebar'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should disable parts' do
|
||||||
|
@parts = [@parts.shift, @parts.pop]
|
||||||
|
@page.send(:update_parts, @parts)
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts[1].name.should == 'Left column'
|
||||||
|
@page.parts[1].disabled.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should enable parts previously disabled' do
|
||||||
|
parts_at_first = @parts.clone
|
||||||
|
@parts = [@parts.shift, @parts.pop]
|
||||||
|
@page.send(:update_parts, @parts)
|
||||||
|
@page.send(:update_parts, parts_at_first)
|
||||||
|
@page.parts.size.should == 3
|
||||||
|
@page.parts[1].name.should == 'Left column'
|
||||||
|
@page.parts[1].disabled.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe 'acts as tree' do
|
describe 'acts as tree' do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@ -125,22 +233,21 @@ describe Page do
|
|||||||
|
|
||||||
describe 'acts as list' do
|
describe 'acts as list' do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@home = Factory(:page)
|
@home = Factory(:page)
|
||||||
@child_1 = Factory(:page, :title => 'Subpage 1', :slug => 'foo', :parent => @home, :site => @home.site)
|
@child_1 = Factory(:page, :title => 'Subpage 1', :slug => 'foo', :parent => @home, :site => @home.site)
|
||||||
@child_2 = Factory(:page, :title => 'Subpage 2', :slug => 'bar', :parent => @home, :site => @home.site)
|
@child_2 = Factory(:page, :title => 'Subpage 2', :slug => 'bar', :parent => @home, :site => @home.site)
|
||||||
@child_3 = Factory(:page, :title => 'Subpage 3', :slug => 'acme', :parent => @home, :site => @home.site)
|
@child_3 = Factory(:page, :title => 'Subpage 3', :slug => 'acme', :parent => @home, :site => @home.site)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should be at the bottom of the folder once created' do
|
it 'should be at the bottom of the folder once created' do
|
||||||
[@child_1, @child_2, @child_3].each_with_index { |c, i| c.position.should == i + 1 }
|
[@child_1, @child_2, @child_3].each_with_index { |c, i| c.position.should == i + 1 }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should have its position updated if a sibling is removed' do
|
||||||
|
@child_2.destroy
|
||||||
|
[@child_1, @child_3.reload].each_with_index { |c, i| c.position.should == i + 1 }
|
||||||
|
end
|
||||||
|
|
||||||
it 'should have its position updated if a sibling is removed' do
|
end
|
||||||
@child_2.destroy
|
|
||||||
[@child_1, @child_3.reload].each_with_index { |c, i| c.position.should == i + 1 }
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
@ -9,7 +9,7 @@ require 'rspec/rails'
|
|||||||
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
||||||
|
|
||||||
Rspec.configure do |config|
|
Rspec.configure do |config|
|
||||||
config.mock_with :rspec
|
config.mock_with :mocha
|
||||||
|
|
||||||
config.before(:each) do
|
config.before(:each) do
|
||||||
Mongoid.master.collections.each(&:drop)
|
Mongoid.master.collections.each(&:drop)
|
||||||
|
Loading…
Reference in New Issue
Block a user