Merge remote-tracking branch 'origin/2.0.0.rc' into 2.0.0.rc

This commit is contained in:
Mario Visic 2012-04-02 19:28:21 +08:00
commit 114218b8c4
41 changed files with 589 additions and 325 deletions

View File

@ -17,18 +17,18 @@ GIT
GIT GIT
remote: git://github.com/locomotivecms/custom_fields.git remote: git://github.com/locomotivecms/custom_fields.git
revision: e54c3ddfce0e24668d02c372c6143a722718e215 revision: 5b0e68859eaca41ac9d7a0231c6cd68ad66018b8
branch: 2.0.0.rc branch: 2.0.0.rc
specs: specs:
custom_fields (2.0.0.rc8) custom_fields (2.0.0.rc9)
activesupport (~> 3.2.1) activesupport (~> 3.2.1)
carrierwave-mongoid (~> 0.1.3) carrierwave-mongoid (~> 0.1.3)
mongoid (~> 2.4.5) mongoid (~> 2.4.7)
PATH PATH
remote: . remote: .
specs: specs:
locomotive_cms (2.0.0.rc2) locomotive_cms (2.0.0.rc3)
RedCloth (~> 4.2.8) RedCloth (~> 4.2.8)
actionmailer-with-request (~> 0.3.0) actionmailer-with-request (~> 0.3.0)
bson_ext (~> 1.5.2) bson_ext (~> 1.5.2)
@ -36,7 +36,7 @@ PATH
carrierwave-mongoid (~> 0.1.3) carrierwave-mongoid (~> 0.1.3)
cells (~> 3.8.0) cells (~> 3.8.0)
codemirror-rails (~> 2.21) codemirror-rails (~> 2.21)
custom_fields (~> 2.0.0.rc8) custom_fields (~> 2.0.0.rc9)
devise (~> 1.5.3) devise (~> 1.5.3)
dragonfly (~> 0.9.8) dragonfly (~> 0.9.8)
flash_cookie_session (~> 1.1.1) flash_cookie_session (~> 1.1.1)
@ -66,14 +66,14 @@ GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
RedCloth (4.2.9) RedCloth (4.2.9)
actionmailer (3.2.1) actionmailer (3.2.2)
actionpack (= 3.2.1) actionpack (= 3.2.2)
mail (~> 2.4.0) mail (~> 2.4.0)
actionmailer-with-request (0.3.0) actionmailer-with-request (0.3.0)
rails (>= 3) rails (>= 3)
actionpack (3.2.1) actionpack (3.2.2)
activemodel (= 3.2.1) activemodel (= 3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
builder (~> 3.0.0) builder (~> 3.0.0)
erubis (~> 2.7.0) erubis (~> 2.7.0)
journey (~> 1.0.1) journey (~> 1.0.1)
@ -81,18 +81,18 @@ GEM
rack-cache (~> 1.1) rack-cache (~> 1.1)
rack-test (~> 0.6.1) rack-test (~> 0.6.1)
sprockets (~> 2.1.2) sprockets (~> 2.1.2)
activemodel (3.2.1) activemodel (3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
builder (~> 3.0.0) builder (~> 3.0.0)
activerecord (3.2.1) activerecord (3.2.2)
activemodel (= 3.2.1) activemodel (= 3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
arel (~> 3.0.0) arel (~> 3.0.2)
tzinfo (~> 0.3.29) tzinfo (~> 0.3.29)
activeresource (3.2.1) activeresource (3.2.2)
activemodel (= 3.2.1) activemodel (= 3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
activesupport (3.2.1) activesupport (3.2.2)
i18n (~> 0.6) i18n (~> 0.6)
multi_json (~> 1.0) multi_json (~> 1.0)
addressable (2.2.7) addressable (2.2.7)
@ -112,10 +112,10 @@ GEM
xpath (~> 0.1.4) xpath (~> 0.1.4)
carrierwave (0.5.8) carrierwave (0.5.8)
activesupport (~> 3.0) activesupport (~> 3.0)
carrierwave-mongoid (0.1.3) carrierwave-mongoid (0.1.4)
carrierwave (>= 0.5.6) carrierwave (~> 0.5.6)
mongoid (~> 2.1) mongoid (~> 2.1)
cells (3.8.2) cells (3.8.3)
actionpack (~> 3.0) actionpack (~> 3.0)
railties (~> 3.0) railties (~> 3.0)
childprocess (0.3.1) childprocess (0.3.1)
@ -140,7 +140,7 @@ GEM
capybara (>= 1.1.2) capybara (>= 1.1.2)
cucumber (>= 1.1.8) cucumber (>= 1.1.8)
nokogiri (>= 1.5.0) nokogiri (>= 1.5.0)
database_cleaner (0.7.1) database_cleaner (0.7.2)
devise (1.5.3) devise (1.5.3)
bcrypt-ruby (~> 3.0) bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.0.3) orm_adapter (~> 0.0.3)
@ -175,7 +175,7 @@ GEM
formtastic (2.0.2) formtastic (2.0.2)
rails (~> 3.0) rails (~> 3.0)
fssm (0.2.8.1) fssm (0.2.8.1)
gherkin (2.9.0) gherkin (2.9.3)
json (>= 1.4.6) json (>= 1.4.6)
haml (3.1.4) haml (3.1.4)
highline (1.6.11) highline (1.6.11)
@ -188,13 +188,13 @@ GEM
jquery-rails (1.0.19) jquery-rails (1.0.19)
railties (~> 3.0) railties (~> 3.0)
thor (~> 0.14) thor (~> 0.14)
json (1.6.5) json (1.6.6)
kaminari (0.13.0) kaminari (0.13.0)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
railties (>= 3.0.0) railties (>= 3.0.0)
kgio (2.7.2) kgio (2.7.4)
launchy (2.0.5) launchy (2.1.0)
addressable (~> 2.2.6) addressable (~> 2.2.6)
locomotive-aloha-rails (0.20.1.1) locomotive-aloha-rails (0.20.1.1)
actionpack (~> 3.2.1) actionpack (~> 3.2.1)
@ -203,16 +203,16 @@ GEM
locomotive-tinymce-rails (3.4.7.1) locomotive-tinymce-rails (3.4.7.1)
actionpack (~> 3.2.1) actionpack (~> 3.2.1)
locomotive_liquid (2.2.2) locomotive_liquid (2.2.2)
mail (2.4.1) mail (2.4.4)
i18n (>= 0.4.0) i18n (>= 0.4.0)
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
mime-types (1.17.2) mime-types (1.18)
mimetype-fu (0.1.2) mimetype-fu (0.1.2)
mocha (0.9.12) mocha (0.9.12)
mongo (1.5.2) mongo (1.5.2)
bson (= 1.5.2) bson (= 1.5.2)
mongoid (2.4.6) mongoid (2.4.7)
activemodel (~> 3.1) activemodel (~> 3.1)
mongo (~> 1.3) mongo (~> 1.3)
tzinfo (~> 0.3.22) tzinfo (~> 0.3.22)
@ -221,34 +221,34 @@ GEM
net-scp (1.0.4) net-scp (1.0.4)
net-ssh (>= 1.99.1) net-ssh (>= 1.99.1)
net-ssh (2.3.0) net-ssh (2.3.0)
nokogiri (1.5.0) nokogiri (1.5.2)
orm_adapter (0.0.6) orm_adapter (0.0.7)
pickle (0.4.10) pickle (0.4.10)
cucumber (>= 0.8) cucumber (>= 0.8)
rake rake
polyglot (0.3.3) polyglot (0.3.3)
rack (1.4.1) rack (1.4.1)
rack-cache (1.1) rack-cache (1.2)
rack (>= 0.4) rack (>= 0.4)
rack-ssl (1.3.2) rack-ssl (1.3.2)
rack rack
rack-test (0.6.1) rack-test (0.6.1)
rack (>= 1.0) rack (>= 1.0)
rails (3.2.1) rails (3.2.2)
actionmailer (= 3.2.1) actionmailer (= 3.2.2)
actionpack (= 3.2.1) actionpack (= 3.2.2)
activerecord (= 3.2.1) activerecord (= 3.2.2)
activeresource (= 3.2.1) activeresource (= 3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
bundler (~> 1.0) bundler (~> 1.0)
railties (= 3.2.1) railties (= 3.2.2)
rails-backbone (0.6.1) rails-backbone (0.6.1)
coffee-script (~> 2.2.0) coffee-script (~> 2.2.0)
ejs (~> 1.0.0) ejs (~> 1.0.0)
railties (>= 3.1.0) railties (>= 3.1.0)
railties (3.2.1) railties (3.2.2)
actionpack (= 3.2.1) actionpack (= 3.2.2)
activesupport (= 3.2.1) activesupport (= 3.2.2)
rack-ssl (~> 1.3.2) rack-ssl (~> 1.3.2)
rake (>= 0.8.7) rake (>= 0.8.7)
rdoc (~> 3.4) rdoc (~> 3.4)
@ -281,14 +281,14 @@ GEM
sanitize (2.0.3) sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6) nokogiri (>= 1.4.4, < 1.6)
sass (3.1.15) sass (3.1.15)
sass-rails (3.2.4) sass-rails (3.2.5)
railties (~> 3.2.0) railties (~> 3.2.0)
sass (>= 3.1.10) sass (>= 3.1.10)
tilt (~> 1.3) tilt (~> 1.3)
selenium-webdriver (2.19.0) selenium-webdriver (2.20.0)
childprocess (>= 0.2.5) childprocess (>= 0.2.5)
ffi (~> 1.0.9) ffi (~> 1.0)
multi_json (~> 1.0.4) multi_json (~> 1.0)
rubyzip rubyzip
shoulda-matchers (1.0.0) shoulda-matchers (1.0.0)
sprockets (2.1.2) sprockets (2.1.2)
@ -301,11 +301,11 @@ GEM
treetop (1.4.10) treetop (1.4.10)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
tzinfo (0.3.31) tzinfo (0.3.32)
uglifier (1.2.3) uglifier (1.2.4)
execjs (>= 0.3.0) execjs (>= 0.3.0)
multi_json (>= 1.0.2) multi_json (>= 1.0.2)
unicorn (4.2.0) unicorn (4.2.1)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)

View File

@ -73,18 +73,20 @@ class Locomotive.Views.ContentEntries.FormView extends Locomotive.Views.Shared.F
new_entry = new Locomotive.Models.ContentEntry(@options["#{name}_new_entry"]) new_entry = new Locomotive.Models.ContentEntry(@options["#{name}_new_entry"])
view = new Locomotive.Views.Shared.Fields.HasManyView model: @model, name: name, new_entry: new_entry, inverse_of: inverse_of view = new Locomotive.Views.Shared.Fields.HasManyView model: @model, name: name, new_entry: new_entry, inverse_of: inverse_of
@_has_many_field_views.push(view) if view.ui_enabled()
@_has_many_field_views.push(view)
@$("##{@model.paramRoot}_#{name}_input label").after(view.render().el) @$("##{@model.paramRoot}_#{name}_input label").after(view.render().el)
enable_many_to_many_fields: -> enable_many_to_many_fields: ->
_.each @model.get('many_to_many_custom_fields'), (field) => _.each @model.get('many_to_many_custom_fields'), (field) =>
name = field[0] name = field[0]
view = new Locomotive.Views.Shared.Fields.ManyToManyView model: @model, name: name, all_entries: @options["all_#{name}_entries"] view = new Locomotive.Views.Shared.Fields.ManyToManyView model: @model, name: name, all_entries: @options["all_#{name}_entries"]
@_many_to_many_field_views.push(view) if view.ui_enabled()
@_many_to_many_field_views.push(view)
@$("##{@model.paramRoot}_#{name}_input label").after(view.render().el) @$("##{@model.paramRoot}_#{name}_input label").after(view.render().el)
slugify_label_field: -> slugify_label_field: ->
@$('li.input.highlighted > input[type=text]').slugify(target: @$('#content_entry__slug')) @$('li.input.highlighted > input[type=text]').slugify(target: @$('#content_entry__slug'))

View File

@ -112,6 +112,11 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
data: { parent_id: @$('#page_parent_id').val(), slug: @$('#page_slug').val() } data: { parent_id: @$('#page_parent_id').val(), slug: @$('#page_slug').val() }
success: (data) => success: (data) =>
@$('#page_slug_input .inline-hints').html(data.url).effect('highlight') @$('#page_slug_input .inline-hints').html(data.url).effect('highlight')
if data.templatized_parent
@$('li#page_slug_input').show()
@$('li#page_templatized_input, li#page_target_klass_name_input').hide()
else
@$('li#page_templatized_input, li#page_target_klass_name_input').show()
enable_response_type_select: -> enable_response_type_select: ->
@$('li#page_response_type_input').change (event) => @$('li#page_response_type_input').change (event) =>
@ -129,6 +134,8 @@ class Locomotive.Views.Pages.FormView extends Locomotive.Views.Shared.FormView
off_callback: => off_callback: =>
@$('li#page_target_klass_name_input').hide() @$('li#page_target_klass_name_input').hide()
@$('li#page_templatized_input').hide() if @model.get('templatized_from_parent') == true
enable_redirect_checkbox: -> enable_redirect_checkbox: ->
@_enable_checkbox 'redirect', @_enable_checkbox 'redirect',
features: ['templatized', 'cache_strategy'] features: ['templatized', 'cache_strategy']

View File

@ -34,6 +34,9 @@ class Locomotive.Views.Shared.Fields.HasManyView extends Backbone.View
return @ return @
ui_enabled: ->
@template()?
insert_entries: -> insert_entries: ->
if @collection.length > 0 if @collection.length > 0
@collection.each (entry) => @insert_entry(entry) @collection.each (entry) => @insert_entry(entry)

View File

@ -39,6 +39,9 @@ class Locomotive.Views.Shared.Fields.ManyToManyView extends Backbone.View
return @ return @
ui_enabled: ->
@template()?
insert_entries: -> insert_entries: ->
if @collection.length > 0 if @collection.length > 0
@collection.each (entry) => @insert_entry(entry) @collection.each (entry) => @insert_entry(entry)

View File

@ -11,6 +11,7 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
min-height: 100%;
} }
} }

View File

@ -53,9 +53,10 @@ module Locomotive
end end
def get_path def get_path
page = current_site.pages.build(:parent => current_site.pages.find(params[:parent_id]), :slug => params[:slug].permalink) page = current_site.pages.build(:parent => current_site.pages.find(params[:parent_id]), :slug => params[:slug].permalink).tap do |p|
page.send(:build_fullpath) p.valid?; p.send(:build_fullpath)
render :json => { :url => public_page_url(page), :slug => page.slug } end
render :json => { :url => public_page_url(page), :slug => page.slug, :templatized_parent => page.templatized_from_parent? }
end end
end end

View File

@ -7,6 +7,7 @@ module Locomotive
included do included do
## fields ##
field :listed, :type => Boolean, :default => true field :listed, :type => Boolean, :default => true
end end

View File

@ -6,17 +6,22 @@ module Locomotive
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
## fields ##
field :serialized_template, :type => Binary, :localize => true field :serialized_template, :type => Binary, :localize => true
field :template_dependencies, :type => Array, :default => [], :localize => true field :template_dependencies, :type => Array, :default => [], :localize => true
field :snippet_dependencies, :type => Array, :default => [], :localize => true field :snippet_dependencies, :type => Array, :default => [], :localize => true
## virtual attributes
attr_reader :template_changed attr_reader :template_changed
## callbacks ##
before_validation :serialize_template before_validation :serialize_template
after_save :update_template_descendants after_save :update_template_descendants
## validations ##
validate :template_must_be_valid validate :template_must_be_valid
## scopes ##
scope :pages, lambda { |domain| { :any_in => { :domains => [*domain] } } } scope :pages, lambda { |domain| { :any_in => { :domains => [*domain] } } }
end end

View File

@ -3,10 +3,90 @@ module Locomotive
module Page module Page
module Render module Render
extend ActiveSupport::Concern
def render(context) def render(context)
self.template.render(context) self.template.render(context)
end end
module ClassMethods
# Given both a site and a path, this method tries
# to get the matching page.
# If the page is templatized, the related content entry is
# associated to the page (page.content_entry stores the entry).
# If no page is found, then it returns the 404 one instead.
#
# @param [ Site ] site The site where to find the page
# @param [ String ] path The fullpath got from the request
# @param [ Boolean ] logged_in True if someone is logged in Locomotive
#
# @return [ Page ] The page matching the criteria OR the 404 one if none
#
def fetch_page_from_path(site, path, logged_in)
page = nil
depth = path == 'index' ? 0 : path.split('/').size
matching_paths = path == 'index' ? %w(index) : path_combinations(path)
site.pages.where(:depth => depth, :fullpath.in => matching_paths).each do |_page|
if !_page.published? && !logged_in
next
else
if _page.templatized?
%r(^#{_page.fullpath.gsub('content_type_template', '([^\/]+)')}$) =~ path
permalink = $1
_page.content_entry = _page.fetch_target_entry(permalink)
if _page.content_entry.nil? || (!_page.content_entry.visible? && !logged_in) # content instance not found or not visible
next
end
end
end
page = _page
break
end
page || site.pages.not_found.published.first
end
# Calculate all the combinations possible based on the
# fact that one of the segment of the path could be
# a content type from a templatized page.
# We postulate that there is only one templatized page in a path
# (ie: no nested templatized pages)
#
# @param [ String ] path The path to the page
#
# @return [ Array ] An array of all the combinations
#
def path_combinations(path)
_path_combinations(path.split('/'))
end
#:nodoc:
def _path_combinations(segments, can_include_template = true)
return nil if segments.empty?
segment = segments.shift
(can_include_template ? [segment, 'content_type_template'] : [segment]).map do |_segment|
if (_combinations = _path_combinations(segments.clone, can_include_template && _segment != 'content_type_template'))
[*_combinations].map do |_combination|
File.join(_segment, _combination)
end
else
[_segment]
end
end.flatten
end
end
end end
end end
end end

View File

@ -7,7 +7,9 @@ module Locomotive
included do included do
field :templatized, :type => Boolean, :default => false ## fields ##
field :templatized, :type => Boolean, :default => false
field :templatized_from_parent, :type => Boolean, :default => false
field :target_klass_name field :target_klass_name
## validations ## ## validations ##
@ -15,11 +17,16 @@ module Locomotive
validate :ensure_target_klass_name_security validate :ensure_target_klass_name_security
## callbacks ## ## callbacks ##
before_validation :get_templatized_from_parent
before_validation :set_slug_if_templatized before_validation :set_slug_if_templatized
before_validation :ensure_target_klass_name_security before_validation :ensure_target_klass_name_security
after_save :propagate_templatized
## scopes ## ## scopes ##
scope :templatized, :where => { :templatized => true } scope :templatized, :where => { :templatized => true }
## virtual attributes ##
attr_accessor :content_entry
end end
# Returns the class specified by the target_klass_name property # Returns the class specified by the target_klass_name property
@ -70,10 +77,26 @@ module Locomotive
protected protected
def set_slug_if_templatized def get_templatized_from_parent
self.slug = 'content_type_template' if self.templatized? return if self.parent.nil?
if self.parent.templatized?
self.templatized = self.templatized_from_parent = true
self.target_klass_name = self.parent.target_klass_name
elsif !self.templatized?
self.templatized = self.templatized_from_parent = false
self.target_klass_name = nil
end
end end
def set_slug_if_templatized
self.slug = 'content_type_template' if self.templatized? && !self.templatized_from_parent?
end
# Makes sure the target_klass is owned by the site OR
# if it belongs to the models allowed by the application
# thanks to the models_for_templatization option.
#
def ensure_target_klass_name_security def ensure_target_klass_name_security
return if !self.templatized? || self.target_klass_name.blank? return if !self.templatized? || self.target_klass_name.blank?
@ -86,7 +109,25 @@ module Locomotive
elsif !Locomotive.config.models_for_templatization.include?(self.target_klass_name) elsif !Locomotive.config.models_for_templatization.include?(self.target_klass_name)
self.errors.add :target_klass_name, :security self.errors.add :target_klass_name, :security
end end
end
# Sets the templatized, templatized_from_parent properties of
# the children of the current page ONLY IF the templatized
# attribute got changed.
#
def propagate_templatized
return unless self.templatized_changed?
selector = { 'parent_ids' => { '$in' => [self._id] } }
operations = {
'$set' => {
'templatized' => self.templatized,
'templatized_from_parent' => self.templatized,
'target_klass_name' => self.target_klass_name
}
}
self.collection.update selector, operations, :multi => true
end end
end end

View File

@ -38,6 +38,10 @@ module Locomotive
Page.quick_tree(self) Page.quick_tree(self)
end end
def fetch_page(path, logged_in)
Locomotive::Page.fetch_page_from_path self, path, logged_in
end
def accounts def accounts
Account.criteria.in(:_id => self.memberships.map(&:account_id)) Account.criteria.in(:_id => self.memberships.map(&:account_id))
end end

View File

@ -43,7 +43,7 @@ module Locomotive
hash[meth]= (if self.source.custom_fields_methods.include?(meth.to_s) hash[meth]= (if self.source.custom_fields_methods.include?(meth.to_s)
if self.source.is_a_custom_field_many_relationship?(meth.to_s) if self.source.is_a_custom_field_many_relationship?(meth.to_s)
# go deeper # go deeper
self.source.send(meth).map { |entry| entry.to_presenter(:depth => self.depth + 1) } self.source.send(meth).ordered.map { |entry| entry.to_presenter(:depth => self.depth + 1) }
else else
self.source.send(meth) rescue nil self.source.send(meth) rescue nil
end end

View File

@ -1,7 +1,7 @@
module Locomotive module Locomotive
class PagePresenter < BasePresenter class PagePresenter < BasePresenter
delegate :title, :slug, :fullpath, :handle, :raw_template, :published, :listed, :templatized, :redirect, :redirect_url, :template_changed, :cache_strategy, :response_type, :to => :source delegate :title, :slug, :fullpath, :handle, :raw_template, :published, :listed, :templatized, :templatized_from_parent, :redirect, :redirect_url, :template_changed, :cache_strategy, :response_type, :to => :source
def escaped_raw_template def escaped_raw_template
h(self.source.raw_template) h(self.source.raw_template)
@ -12,7 +12,7 @@ module Locomotive
end end
def included_methods def included_methods
super + %w(title slug fullpath handle raw_template published listed templatized redirect redirect_url cache_strategy response_type template_changed editable_elements localized_fullpaths) super + %w(title slug fullpath handle raw_template published listed templatized templatized_from_parent redirect redirect_url cache_strategy response_type template_changed editable_elements localized_fullpaths)
end end
def localized_fullpaths def localized_fullpaths

View File

@ -16,7 +16,7 @@
- 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
= f.input :slug, :required => false, :hint => @page.slug.blank? ? t('.empty_slug') : public_page_url(@page), :input_html => { :'data-url' => get_path_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized?};", :class => 'em-inline-hints' } = f.input :slug, :required => false, :hint => @page.slug.blank? ? t('.empty_slug') : public_page_url(@page), :input_html => { :'data-url' => get_path_pages_url, :disabled => @page.index? || @page.not_found? }, :wrapper_html => { :style => "#{'display: none' if @page.templatized? && !@page.templatized_from_parent?};", :class => 'em-inline-hints' }
= f.inputs :name => :seo, :class => "inputs foldable #{'folded' if inputs_folded?(@page)}" do = f.inputs :name => :seo, :class => "inputs foldable #{'folded' if inputs_folded?(@page)}" do
@ -34,9 +34,9 @@
= f.input :response_type, :as => :select, :collection => options_for_page_response_type, :include_blank => false = f.input :response_type, :as => :select, :collection => options_for_page_response_type, :include_blank => false
= f.input :templatized, :as => :'Locomotive::Toggle', :style => "#{'display: none' if @page.redirect?}" = f.input :templatized, :as => :'Locomotive::Toggle', :style => "#{'display: none' if @page.redirect? || @page.templatized_from_parent?}"
= f.input :target_klass_name, :as => :select, :collection => options_for_target_klass_name, :include_blank => false, :wrapper_html => { :style => "#{'display: none' unless @page.templatized?}" } = f.input :target_klass_name, :as => :select, :collection => options_for_target_klass_name, :include_blank => false, :wrapper_html => { :style => "#{'display: none' unless @page.templatized? && !@page.templatized_from_parent?}" }
= f.input :published, :as => :'Locomotive::Toggle' = f.input :published, :as => :'Locomotive::Toggle'

View File

@ -289,3 +289,18 @@ fr:
explanations: "Si vous avez déjà uploadé le template de site par défaut (voir instructions), vous pouvez l'utiliser dès maintenant. Ou vous pouvez uploader un template de site sous forme d'un fichier zip (quelques themes disponibles <a href=\"http://www.locomotivecms.com/support/themes\">ici</a>)." explanations: "Si vous avez déjà uploadé le template de site par défaut (voir instructions), vous pouvez l'utiliser dès maintenant. Ou vous pouvez uploader un template de site sous forme d'un fichier zip (quelques themes disponibles <a href=\"http://www.locomotivecms.com/support/themes\">ici</a>)."
back_to_default_template: "Cliquez <a href='#'>ici</a> pour sélectionner à la place le template de site par défault" back_to_default_template: "Cliquez <a href='#'>ici</a> pour sélectionner à la place le template de site par défault"
next: Créer site next: Créer site
public:
pages:
show_toolbar:
statuses:
loading: "Chargement...."
disabled: "Editeur en ligne désactivé"
labels:
save_changes: "Enregistrez les modifications: "
editing_mode: "Mode édition: "
lang: "Langue: "
buttons:
back: "Revenir au panneau d'administration"
confirm: "Valider"
cancel: "Annuler"

View File

@ -0,0 +1,52 @@
Feature: Many to Many Association
As a designer
In order to make dealing with models easier
I want to be able to display other models that have a many to many association
Background:
Given I have the site: "test site" set up
And I have a custom model named "Articles" with
| label | type | required |
| Title | string | true |
| Body | string | false |
And I have a custom model named "Projects" with
| label | type | required | target |
| Name | string | true | |
| Description | text | false | |
And I set up a many_to_many relationship between "Articles" and "Projects"
And I have entries for "Articles" with
| title | body |
| Hello world | Lorem ipsum |
| Lorem ipsum | Lorem ipsum... |
And I have entries for "Projects" with
| name | description |
| My sexy project | Lorem ipsum |
| Foo project | Lorem ipsum... |
| Bar project | Lorem ipsum... |
| Baz project | Lorem ipsum... |
And I attach the "My sexy project" project to the "Hello world" article
And I attach the "Baz project" project to the "Hello world" article
And I attach the "Foo project" project to the "Hello world" article
Scenario: Displaying the entries of a many to many association
Given a page named "article-projects" with the template:
"""
{% assign article = contents.articles.first %}
<h1>Projects for {{ article.title }}</h1>
<ul>
{% for project in article.projects %}<li>{{ project.name }}</li>
{% endfor %}
</ul>
"""
When I view the rendered page at "/article-projects"
Then the rendered output should look like:
"""
<h1>Projects for Hello world</h1>
<ul>
<li>My sexy project</li>
<li>Baz project</li>
<li>Foo project</li>
</ul>
"""

View File

@ -18,7 +18,7 @@ Then /^I should be able to display paginated models$/ do
{{ paginate | default_pagination }} {{ paginate | default_pagination }}
{% endpaginate %} {% endpaginate %}
} }
FactoryGirl.create(:page, :site => @site, :slug => 'hello', :raw_template => raw_template) FactoryGirl.create(:page, :site => @site, :slug => 'hello', :parent => @site.pages.root.first, :raw_template => raw_template)
# The page should have the first two articles # The page should have the first two articles
visit '/hello' visit '/hello'

View File

@ -41,7 +41,7 @@ Given %r{^I set up a many_to_many relationship between "([^"]*)" and "([^"]*)"$}
:label => last_name, :label => last_name,
:type => 'many_to_many', :type => 'many_to_many',
:class_name => last_model.klass_with_custom_fields(:entries).to_s, :class_name => last_model.klass_with_custom_fields(:entries).to_s,
:inverse_of => first_name.singularize.downcase :inverse_of => first_name.pluralize.downcase
}) })
first_model.save first_model.save
@ -50,12 +50,22 @@ Given %r{^I set up a many_to_many relationship between "([^"]*)" and "([^"]*)"$}
:label => first_name, :label => first_name,
:type => 'many_to_many', :type => 'many_to_many',
:class_name => first_model.klass_with_custom_fields(:entries).to_s, :class_name => first_model.klass_with_custom_fields(:entries).to_s,
:inverse_of => last_name.singularize.downcase :inverse_of => last_name.pluralize.downcase
}) })
last_model.save last_model.save
end end
Given %r{^I attach the "([^"]*)" ([\S]*) to the "([^"]*)" ([\S]*)$} do |target_name, target_model_name, souce_name, source_model_name|
target_model = @site.content_types.where(:name => target_model_name.pluralize.capitalize).first
source_model = @site.content_types.where(:name => source_model_name.pluralize.capitalize).first
target_entry = target_model.entries.where(:_slug => target_name.permalink).first
source_entry = source_model.entries.where(:_slug => souce_name.permalink).first
source_entry.send(target_model_name.pluralize.downcase.parameterize('_').to_sym).push(target_entry)
end
Then /^I should be able to view a paginated list of a has many association$/ do Then /^I should be able to view a paginated list of a has many association$/ do
# Create models # Create models
step %{I have an "Articles" model which has many "Comments"} step %{I have an "Articles" model which has many "Comments"}
@ -80,7 +90,7 @@ Then /^I should be able to view a paginated list of a has many association$/ do
} }
# Create a page # Create a page
FactoryGirl.create(:page, :site => @site, :slug => 'hello', :raw_template => raw_template) FactoryGirl.create(:page, :site => @site, :slug => 'hello', :parent => @site.pages.root.first, :raw_template => raw_template)
# The page should have the first two comments # The page should have the first two comments
visit '/hello' visit '/hello'

View File

@ -18,7 +18,7 @@ module Locomotive
::ActionController::Base.wrap_parameters :format => [:json] ::ActionController::Base.wrap_parameters :format => [:json]
end end
initializer "Locomotive precompile hook" do |app| initializer "Locomotive precompile hook", :group => :all do |app|
app.config.assets.precompile += %w(locomotive.js locomotive.css locomotive/inline_editor.js locomotive/inline_editor.css app.config.assets.precompile += %w(locomotive.js locomotive.css locomotive/inline_editor.js locomotive/inline_editor.css
locomotive/not_logged_in.js locomotive/not_logged_in.css locomotive/not_logged_in.js locomotive/not_logged_in.css
locomotive/aloha.js) locomotive/aloha.js)

View File

@ -1,5 +1,6 @@
require 'locomotive/liquid/drops/base' require 'locomotive/liquid/drops/base'
require 'locomotive/liquid/drops/proxy_collection' require 'locomotive/liquid/drops/proxy_collection'
require 'locomotive/liquid/filters/base'
%w{. tags drops filters}.each do |dir| %w{. tags drops filters}.each do |dir|
Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib } Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }

View File

@ -45,7 +45,7 @@ module Locomotive
if not @@forbidden_attributes.include?(meth.to_s) if not @@forbidden_attributes.include?(meth.to_s)
value = self._source.send(meth) value = self._source.send(meth)
if value.respond_to?(:all) if value.respond_to?(:all) # check for an association
filter_and_order_list(value) filter_and_order_list(value)
else else
value value
@ -70,7 +70,7 @@ module Locomotive
end end
else else
list.ordered list.ordered
end.all end
end end
end end

View File

@ -0,0 +1,47 @@
module Locomotive
module Liquid
module Filters
module Base
protected
# Convert an array of properties ('key:value') into a hash
# Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
def args_to_options(*args)
options = {}
args.flatten.each do |a|
if (a =~ /^(.*):(.*)$/)
options[$1.to_sym] = $2
end
end
options
end
# Write options (Hash) into a string according to the following pattern:
# <key1>="<value1>", <key2>="<value2", ...etc
def inline_options(options = {})
return '' if options.empty?
(options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
end
# Get the url to be used in html tags such as image_tag, flash_tag, ...etc
# input: url (String) OR asset drop
def get_url_from_asset(input)
input.respond_to?(:url) ? input.url : input
end
def asset_url(path)
ThemeAssetUploader.url_for(@context.registers[:site], path)
end
def absolute_url(url)
url.starts_with?('/') ? url : "/#{url}"
end
end
::Liquid::Template.register_filter(Base)
end
end
end

View File

@ -6,7 +6,7 @@ module Locomotive
# Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed. # Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed.
# input: url of the feed # input: url of the feed
# example: # example:
# {{ '/foo/bar' | auto_discovery_link_tag: 'rel:alternate', 'type:atom', 'title:A title' }} # {{ '/foo/bar' | auto_discovery_link_tag: 'rel:alternate', 'type:application/atom+xml', 'title:A title' }}
def auto_discovery_link_tag(input, *args) def auto_discovery_link_tag(input, *args)
options = args_to_options(args) options = args_to_options(args)
@ -17,7 +17,7 @@ module Locomotive
%{<link rel="#{rel}" type="#{type}" title="#{title}" href="#{input}" />} %{<link rel="#{rel}" type="#{type}" title="#{title}" href="#{input}" />}
end end
# Write the url to a stylesheet resource # Write the url of a theme stylesheet
# input: name of the css file # input: name of the css file
def stylesheet_url(input) def stylesheet_url(input)
return '' if input.nil? return '' if input.nil?
@ -31,7 +31,7 @@ module Locomotive
input input
end end
# Write the link to a stylesheet resource # Write the link tag of a theme stylesheet
# input: url of the css file # input: url of the css file
def stylesheet_tag(input, media = 'screen') def stylesheet_tag(input, media = 'screen')
return '' if input.nil? return '' if input.nil?
@ -106,80 +106,6 @@ module Locomotive
}.gsub(/ >/, '>').strip }.gsub(/ >/, '>').strip
end end
# Render the navigation for a paginated collection
def default_pagination(paginate, *args)
return '' if paginate['parts'].empty?
options = args_to_options(args)
previous_label = options[:previous_label] || I18n.t('pagination.previous')
next_label = options[:next_label] || I18n.t('pagination.next')
previous_link = (if paginate['previous'].blank?
"<span class=\"disabled prev_page\">#{previous_label}</span>"
else
"<a href=\"#{absolute_url(paginate['previous']['url'])}\" class=\"prev_page\">#{previous_label}</a>"
end)
links = ""
paginate['parts'].each do |part|
links << (if part['is_link']
"<a href=\"#{absolute_url(part['url'])}\">#{part['title']}</a>"
elsif part['hellip_break']
"<span class=\"gap\">#{part['title']}</span>"
else
"<span class=\"current\">#{part['title']}</span>"
end)
end
next_link = (if paginate['next'].blank?
"<span class=\"disabled next_page\">#{next_label}</span>"
else
"<a href=\"#{absolute_url(paginate['next']['url'])}\" class=\"next_page\">#{next_label}</a>"
end)
%{<div class="pagination #{options[:css]}">
#{previous_link}
#{links}
#{next_link}
</div>}
end
protected
# Convert an array of properties ('key:value') into a hash
# Ex: ['width:50', 'height:100'] => { :width => '50', :height => '100' }
def args_to_options(*args)
options = {}
args.flatten.each do |a|
if (a =~ /^(.*):(.*)$/)
options[$1.to_sym] = $2
end
end
options
end
# Write options (Hash) into a string according to the following pattern:
# <key1>="<value1>", <key2>="<value2", ...etc
def inline_options(options = {})
return '' if options.empty?
(options.stringify_keys.sort.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
end
# Get the url to be used in html tags such as image_tag, flash_tag, ...etc
# input: url (String) OR asset drop
def get_url_from_asset(input)
input.respond_to?(:url) ? input.url : input
end
def asset_url(path)
ThemeAssetUploader.url_for(@context.registers[:site], path)
end
def absolute_url(url)
url.starts_with?('/') ? url : "/#{url}"
end
end end
::Liquid::Template.register_filter(Html) ::Liquid::Template.register_filter(Html)

View File

@ -3,24 +3,6 @@ module Locomotive
module Filters module Filters
module Misc module Misc
def underscore(input)
input.to_s.gsub(' ', '_').gsub('/', '_').underscore
end
def dasherize(input)
input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
end
def multi_line(input)
input.to_s.gsub("\n", '<br/>')
end
def concat(input, *args)
result = input.to_s
args.flatten.each { |a| result << a.to_s }
result
end
def modulo(word, index, modulo) def modulo(word, index, modulo)
(index.to_i + 1) % modulo == 0 ? word : '' (index.to_i + 1) % modulo == 0 ? word : ''
end end
@ -33,6 +15,49 @@ module Locomotive
input.last input.last
end end
def default(input, value)
input.blank? ? value : input
end
# Render the navigation for a paginated collection
def default_pagination(paginate, *args)
return '' if paginate['parts'].empty?
options = args_to_options(args)
previous_label = options[:previous_label] || I18n.t('pagination.previous')
next_label = options[:next_label] || I18n.t('pagination.next')
previous_link = (if paginate['previous'].blank?
"<span class=\"disabled prev_page\">#{previous_label}</span>"
else
"<a href=\"#{absolute_url(paginate['previous']['url'])}\" class=\"prev_page\">#{previous_label}</a>"
end)
links = ""
paginate['parts'].each do |part|
links << (if part['is_link']
"<a href=\"#{absolute_url(part['url'])}\">#{part['title']}</a>"
elsif part['hellip_break']
"<span class=\"gap\">#{part['title']}</span>"
else
"<span class=\"current\">#{part['title']}</span>"
end)
end
next_link = (if paginate['next'].blank?
"<span class=\"disabled next_page\">#{next_label}</span>"
else
"<a href=\"#{absolute_url(paginate['next']['url'])}\" class=\"next_page\">#{next_label}</a>"
end)
%{<div class="pagination #{options[:css]}">
#{previous_link}
#{links}
#{next_link}
</div>}
end
end end
::Liquid::Template.register_filter(Misc) ::Liquid::Template.register_filter(Misc)

View File

@ -3,6 +3,24 @@ module Locomotive
module Filters module Filters
module Text module Text
def underscore(input)
input.to_s.gsub(' ', '_').gsub('/', '_').underscore
end
def dasherize(input)
input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
end
def multi_line(input)
input.to_s.gsub("\n", '<br/>')
end
def concat(input, *args)
result = input.to_s
args.flatten.each { |a| result << a.to_s }
result
end
def textile(input) def textile(input)
::RedCloth.new(input).to_html ::RedCloth.new(input).to_html
end end

View File

@ -1,21 +0,0 @@
module Locomotive
module Liquid
module Tags
class Blueprint < ::Liquid::Tag
def render(context)
%{
<link href="/stylesheets/admin/blueprint/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
<link href="/stylesheets/admin/blueprint/print.css" media="print" rel="stylesheet" type="text/css" />
<!--[if IE]>
<link href="/stylesheets/admin/blueprint/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
<![endif]-->
}
end
end
::Liquid::Template.register_tag('blueprint_stylesheets', Blueprint)
end
end
end

View File

@ -1,17 +0,0 @@
module Liquid
module Locomotive
module Tags
class Jquery < ::Liquid::Tag
def render(context)
%{
<script src="/javascripts/jquery.js" type="text/javascript"></script>
<script src="/javascripts/jquery.ui.js" type="text/javascript"></script>
}
end
end
::Liquid::Template.register_tag('jQuery', Jquery)
end
end
end

View File

@ -11,11 +11,11 @@ module Locomotive
# #
# options: # options:
# - label: iso (de, fr, en, ...etc), locale (Deutsch, Français, English, ...etc), title (page title) # - label: iso (de, fr, en, ...etc), locale (Deutsch, Français, English, ...etc), title (page title)
# - sep: piece of html code seperating 2 locales # - sep: piece of html code separating 2 locales
# #
# notes: # notes:
# - "iso" is the default choice for label # - "iso" is the default choice for label
# - " | " is the default seperating code # - " | " is the default separating code
# #
class LocaleSwitcher < ::Liquid::Tag class LocaleSwitcher < ::Liquid::Tag

View File

@ -46,7 +46,7 @@ module Locomotive
end end
page_count, current_page = pagination['total_pages'], pagination['current_page'] page_count, current_page = pagination['total_pages'], pagination['current_page']
path = context['path'] path = sanitize_path(context['fullpath'])
pagination['previous'] = link(I18n.t('pagination.previous'), current_page - 1, path) if pagination['previous_page'] pagination['previous'] = link(I18n.t('pagination.previous'), current_page - 1, path) if pagination['previous_page']
pagination['next'] = link(I18n.t('pagination.next'), current_page + 1, path) if pagination['next_page'] pagination['next'] = link(I18n.t('pagination.next'), current_page + 1, path) if pagination['next_page']
@ -83,6 +83,12 @@ module Locomotive
private private
def sanitize_path(path)
_path = path.gsub(/page=[0-9]+&?/, '').gsub(/_pjax=true&?/, '')
_path = _path.slice(0..-2) if _path.last == '?' || _path.last == '&'
_path
end
def window_size def window_size
3 3
end end
@ -92,7 +98,8 @@ module Locomotive
end end
def link(title, page, path) def link(title, page, path)
{ 'title' => title, 'url' => path + "?page=#{page}", 'is_link' => true} _path = %(#{path}#{path.include?('?') ? '&' : '?'}page=#{page})
{ 'title' => title, 'url' => _path, 'is_link' => true }
end end
end end

View File

@ -16,7 +16,7 @@ module Locomotive
onmouseover="this.style.backgroundPosition='0px -45px'" onmouseover="this.style.backgroundPosition='0px -45px'"
onmousedown="this.style.backgroundPosition='0px -90px'" onmousedown="this.style.backgroundPosition='0px -90px'"
onmouseup="this.style.backgroundPosition='0px 0px'" onmouseup="this.style.backgroundPosition='0px 0px'"
style="display: block;position:fixed;top: 10px; right: 10px;width: 48px; height: 45px;text-indent:-9999px;text-decoration:none;background: transparent url\('/assets/locomotive/icons/start.png'\) no-repeat 0 0;"> style="display: block;z-index: 1031;position: fixed;top: 10px; right: 10px;width: 48px; height: 45px;text-indent:-9999px;text-decoration:none;background: transparent url\('/assets/locomotive/icons/start.png'\) no-repeat 0 0;">
Admin</a> Admin</a>
</body> </body>
) )

View File

@ -26,28 +26,7 @@ module Locomotive
end end
def locomotive_page def locomotive_page
page = nil current_site.fetch_page self.locomotive_page_path, current_locomotive_account.present?
path = self.locomotive_page_path
current_site.pages.any_in(:fullpath => [*path]).each do |_page|
if not _page.published? and current_locomotive_account.nil?
next
else
if _page.templatized?
@content_entry = _page.fetch_target_entry(File.basename(path.first))
if @content_entry.nil? || (!@content_entry.visible? && current_locomotive_account.nil?) # content instance not found or not visible
next
end
end
end
page = _page
break
end
page || not_found_page
end end
def locomotive_page_path def locomotive_page_path
@ -58,11 +37,6 @@ module Locomotive
path = 'index' if path.blank? || path == '_edit' path = 'index' if path.blank? || path == '_edit'
if path != 'index'
dirname = File.dirname(path).gsub(/^\.$/, '') # also look for templatized page path
path = [path, File.join(dirname, 'content_type_template').gsub(/^\//, '')]
end
path path
end end
@ -75,6 +49,7 @@ module Locomotive
'current_page' => self.params[:page], 'current_page' => self.params[:page],
'params' => self.params, 'params' => self.params,
'path' => request.path, 'path' => request.path,
'fullpath' => request.fullpath,
'url' => request.url, 'url' => request.url,
'now' => Time.now.utc, 'now' => Time.now.utc,
'today' => Date.today, 'today' => Date.today,
@ -88,8 +63,8 @@ module Locomotive
assigns.merge!(flash.to_hash.stringify_keys) # data from public submissions assigns.merge!(flash.to_hash.stringify_keys) # data from public submissions
if @page.templatized? # add instance from content type if @page.templatized? # add instance from content type
assigns['entry'] = @content_entry assigns['entry'] = @page.content_entry
assigns[@page.target_entry_name] = @content_entry # just here to help to write readable liquid code assigns[@page.target_entry_name] = @page.content_entry # just here to help to write readable liquid code
end end
registers = { registers = {
@ -120,10 +95,6 @@ module Locomotive
render :text => output, :layout => false, :status => page_status unless performed? render :text => output, :layout => false, :status => page_status unless performed?
end end
def not_found_page
current_site.pages.not_found.published.first
end
def editing_page? def editing_page?
!!@editing !!@editing
end end

View File

@ -1,3 +1,3 @@
module Locomotive #:nodoc module Locomotive #:nodoc
VERSION = '2.0.0.rc2' VERSION = '2.0.0.rc3'
end end

View File

@ -30,7 +30,7 @@ Gem::Specification.new do |s|
s.add_dependency 'mongoid', '~> 2.4.5' s.add_dependency 'mongoid', '~> 2.4.5'
s.add_dependency 'locomotive-mongoid-tree', '~> 0.6.2' s.add_dependency 'locomotive-mongoid-tree', '~> 0.6.2'
s.add_dependency 'custom_fields', '~> 2.0.0.rc8' s.add_dependency 'custom_fields', '~> 2.0.0.rc9'
s.add_dependency 'kaminari', '~> 0.13.0' s.add_dependency 'kaminari', '~> 0.13.0'

View File

@ -13,7 +13,7 @@ describe Locomotive::Liquid::Drops::ContentEntry do
it 'loops through the list' do it 'loops through the list' do
template = %({% for project in category.projects %}{{ project }},{% endfor %}) template = %({% for project in category.projects %}{{ project }},{% endfor %})
@list.expects(:ordered).returns(mock('criteria', :all => %w(a b))) @list.expects(:ordered).returns(%w(a b))
render(template, { 'category' => @category }).should == 'a,b,' render(template, { 'category' => @category }).should == 'a,b,'
end end
@ -21,7 +21,7 @@ describe Locomotive::Liquid::Drops::ContentEntry do
it 'filters the list' do it 'filters the list' do
template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %}) template = %({% with_scope order_by: 'name ASC', active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
@list.expects(:order_by).with(['name', 'ASC']).returns(mock('criteria', :all => %w(a b))) @list.expects(:order_by).with(['name', 'ASC']).returns(%w(a b))
@list.expects(:where).with({ 'active' => true }).returns(@list) @list.expects(:where).with({ 'active' => true }).returns(@list)
render(template, { 'category' => @category }).should == 'a,b,' render(template, { 'category' => @category }).should == 'a,b,'
@ -30,7 +30,7 @@ describe Locomotive::Liquid::Drops::ContentEntry do
it 'filters the list and uses the default order' do it 'filters the list and uses the default order' do
template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %}) template = %({% with_scope active: true %}{% for project in category.projects %}{{ project }},{% endfor %}{% endwith_scope %})
@list.expects(:ordered).returns(mock('criteria', :all => %w(a b))) @list.expects(:ordered).returns(%w(a b))
@list.expects(:where).with({ 'active' => true }).returns(@list) @list.expects(:where).with({ 'active' => true }).returns(@list)
render(template, { 'category' => @category }).should == 'a,b,' render(template, { 'category' => @category }).should == 'a,b,'

View File

@ -2,6 +2,7 @@ require 'spec_helper'
describe Locomotive::Liquid::Filters::Html do describe Locomotive::Liquid::Filters::Html do
include Locomotive::Liquid::Filters::Base
include Locomotive::Liquid::Filters::Html include Locomotive::Liquid::Filters::Html
before(:each) do before(:each) do
@ -206,39 +207,6 @@ describe Locomotive::Liquid::Filters::Html do
}.strip }.strip
end end
it 'should return a navigation block for the pagination' do
pagination = {
"previous" => nil,
"parts" => [
{ 'title' => '1', 'is_link' => false },
{ 'title' => '2', 'is_link' => true, 'url' => '/?page=2' },
{ 'title' => '&hellip;', 'is_link' => false, 'hellip_break' => true },
{ 'title' => '5', 'is_link' => true, 'url' => '/?page=5' }
],
"next" => { 'title' => 'next', 'is_link' => true, 'url' => '/?page=2' }
}
html = default_pagination(pagination, 'css:flickr_pagination')
html.should match(/<div class="pagination flickr_pagination">/)
html.should match(/<span class="disabled prev_page">&laquo; Previous<\/span>/)
html.should match(/<a href="\/\?page=2">2<\/a>/)
html.should match(/<span class=\"gap\">\&hellip;<\/span>/)
html.should match(/<a href="\/\?page=2" class="next_page">Next &raquo;<\/a>/)
pagination.merge!({
'previous' => { 'title' => 'previous', 'is_link' => true, 'url' => '/?page=4' },
'next' => nil
})
html = default_pagination(pagination, 'css:flickr_pagination')
html.should_not match(/<span class="disabled prev_page">&laquo; Previous<\/span>/)
html.should match(/<a href="\/\?page=4" class="prev_page">&laquo; Previous<\/a>/)
html.should match(/<span class="disabled next_page">Next &raquo;<\/span>/)
pagination.merge!({ 'parts' => [] })
html = default_pagination(pagination, 'css:flickr_pagination')
html.should == ''
end
def build_context def build_context
klass = Class.new klass = Class.new
klass.class_eval do klass.class_eval do

View File

@ -2,29 +2,10 @@ require 'spec_helper'
describe Locomotive::Liquid::Filters::Misc do describe Locomotive::Liquid::Filters::Misc do
include Locomotive::Liquid::Filters::Base
include Locomotive::Liquid::Filters::Misc include Locomotive::Liquid::Filters::Misc
it 'should underscore an input' do it 'returns the input string every n occurences' do
underscore('foo').should == 'foo'
underscore('home page').should == 'home_page'
underscore('My foo Bar').should == 'my_foo_bar'
underscore('foo/bar').should == 'foo_bar'
underscore('foo/bar/index').should == 'foo_bar_index'
end
it 'should dasherize an input' do
dasherize('foo').should == 'foo'
dasherize('foo_bar').should == 'foo-bar'
dasherize('foo/bar').should == 'foo-bar'
dasherize('foo/bar/index').should == 'foo-bar-index'
end
it 'should concat strings' do
concat('foo', 'bar').should == 'foobar'
concat('hello', 'foo', 'bar').should == 'hellofoobar'
end
it 'should return the input string every n occurences' do
modulo('foo', 0, 3).should == '' modulo('foo', 0, 3).should == ''
modulo('foo', 1, 3).should == '' modulo('foo', 1, 3).should == ''
modulo('foo', 2, 3).should == 'foo' modulo('foo', 2, 3).should == 'foo'
@ -33,4 +14,43 @@ describe Locomotive::Liquid::Filters::Misc do
modulo('foo', 5, 3).should == 'foo' modulo('foo', 5, 3).should == 'foo'
end end
it 'returns default values if the input is empty' do
default('foo', 42).should == 'foo'
default('', 42).should == 42
default(nil, 42).should == 42
end
it 'should return a navigation block for the pagination' do
pagination = {
"previous" => nil,
"parts" => [
{ 'title' => '1', 'is_link' => false },
{ 'title' => '2', 'is_link' => true, 'url' => '/?page=2' },
{ 'title' => '&hellip;', 'is_link' => false, 'hellip_break' => true },
{ 'title' => '5', 'is_link' => true, 'url' => '/?page=5' }
],
"next" => { 'title' => 'next', 'is_link' => true, 'url' => '/?page=2' }
}
html = default_pagination(pagination, 'css:flickr_pagination')
html.should match(/<div class="pagination flickr_pagination">/)
html.should match(/<span class="disabled prev_page">&laquo; Previous<\/span>/)
html.should match(/<a href="\/\?page=2">2<\/a>/)
html.should match(/<span class=\"gap\">\&hellip;<\/span>/)
html.should match(/<a href="\/\?page=2" class="next_page">Next &raquo;<\/a>/)
pagination.merge!({
'previous' => { 'title' => 'previous', 'is_link' => true, 'url' => '/?page=4' },
'next' => nil
})
html = default_pagination(pagination, 'css:flickr_pagination')
html.should_not match(/<span class="disabled prev_page">&laquo; Previous<\/span>/)
html.should match(/<a href="\/\?page=4" class="prev_page">&laquo; Previous<\/a>/)
html.should match(/<span class="disabled next_page">Next &raquo;<\/span>/)
pagination.merge!({ 'parts' => [] })
html = default_pagination(pagination, 'css:flickr_pagination')
html.should == ''
end
end end

View File

@ -8,4 +8,25 @@ describe Locomotive::Liquid::Filters::Text do
textile('This is *my* text.').should == "<p>This is <strong>my</strong> text.</p>" textile('This is *my* text.').should == "<p>This is <strong>my</strong> text.</p>"
end end
it 'underscores an input' do
underscore('foo').should == 'foo'
underscore('home page').should == 'home_page'
underscore('My foo Bar').should == 'my_foo_bar'
underscore('foo/bar').should == 'foo_bar'
underscore('foo/bar/index').should == 'foo_bar_index'
end
it 'dasherizes an input' do
dasherize('foo').should == 'foo'
dasherize('foo_bar').should == 'foo-bar'
dasherize('foo/bar').should == 'foo-bar'
dasherize('foo/bar/index').should == 'foo-bar-index'
end
it 'concats strings' do
concat('foo', 'bar').should == 'foobar'
concat('hello', 'foo', 'bar').should == 'hellofoobar'
end
end end

View File

@ -2,14 +2,14 @@ require 'spec_helper'
describe Locomotive::Liquid::Tags::Paginate do describe Locomotive::Liquid::Tags::Paginate do
it 'should have a valid syntax' do it 'has a valid syntax' do
markup = "contents.projects by 5" markup = "contents.projects by 5"
lambda do lambda do
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {}) Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {})
end.should_not raise_error end.should_not raise_error
end end
it 'should raise an error if the syntax is incorrect' do it 'raises an error if the syntax is incorrect' do
["contents.projects by a", "contents.projects", "contents.projects 5"].each do |markup| ["contents.projects by a", "contents.projects", "contents.projects 5"].each do |markup|
lambda do lambda do
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {}) Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {})
@ -17,9 +17,9 @@ describe Locomotive::Liquid::Tags::Paginate do
end end
end end
it 'should paginate the collection' do it 'paginates the collection' do
template = Liquid::Template.parse(default_template) template = Liquid::Template.parse(default_template)
text = template.render!(liquid_context) text = template.render!(liquid_context)
text.should match /!Ruby on Rails!/ text.should match /!Ruby on Rails!/
text.should match /!jQuery!/ text.should match /!jQuery!/
@ -33,7 +33,7 @@ describe Locomotive::Liquid::Tags::Paginate do
text.should_not match /!sqlite3!/ text.should_not match /!sqlite3!/
end end
it 'should not paginate if collection is nil or empty' do it 'does not paginate if collection is nil or empty' do
template = Liquid::Template.parse(default_template) template = Liquid::Template.parse(default_template)
lambda do lambda do
@ -45,6 +45,20 @@ describe Locomotive::Liquid::Tags::Paginate do
end.should raise_error end.should raise_error
end end
it 'keeps the original GET parameters' do
context = liquid_context(:fullpath => '/products?foo=1&bar=1&baz=1')
template = Liquid::Template.parse(default_template)
text = template.render!(context)
text.should match /\/products\?foo=1&bar=1&baz=1&page=2/
end
it 'does not include twice the page parameter' do
context = liquid_context(:fullpath => '/products?page=1')
template = Liquid::Template.parse(default_template)
text = template.render!(context)
text.should match /\/products\?page=2/
end
# ___ helpers methods ___ # # ___ helpers methods ___ #
def liquid_context(options = {}) def liquid_context(options = {})
@ -53,7 +67,8 @@ describe Locomotive::Liquid::Tags::Paginate do
{ {
'projects' => options.has_key?(:collection) ? options[:collection] : PaginatedCollection.new(['Ruby on Rails', 'jQuery', 'mongodb', 'Liquid', 'sqlite3']), 'projects' => options.has_key?(:collection) ? options[:collection] : PaginatedCollection.new(['Ruby on Rails', 'jQuery', 'mongodb', 'Liquid', 'sqlite3']),
'current_page' => options[:page] || 1, 'current_page' => options[:page] || 1,
'path' => '/' 'path' => '/',
'fullpath' => options[:fullpath] || '/'
}, { }, {
:page => FactoryGirl.build(:page) :page => FactoryGirl.build(:page)
}, true) }, true)
@ -64,6 +79,7 @@ describe Locomotive::Liquid::Tags::Paginate do
{% for project in paginate.collection %} {% for project in paginate.collection %}
!{{ project }}! !{{ project }}!
{% endfor %} {% endfor %}
{{ paginate.next.url }}
{% endpaginate %}" {% endpaginate %}"
end end

View File

@ -83,25 +83,25 @@ describe 'Locomotive rendering system' do
it 'should retrieve the index page /' do it 'should retrieve the index page /' do
@controller.request.fullpath = '/' @controller.request.fullpath = '/'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{index} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 0, :fullpath.in => %w{index}).returns([@page])
@controller.send(:locomotive_page).should_not be_nil @controller.send(:locomotive_page).should_not be_nil
end end
it 'should also retrieve the index page (index.html)' do it 'should also retrieve the index page (index.html)' do
@controller.request.fullpath = '/index.html' @controller.request.fullpath = '/index.html'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{index} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 0, :fullpath.in => %w{index}).returns([@page])
@controller.send(:locomotive_page).should_not be_nil @controller.send(:locomotive_page).should_not be_nil
end end
it 'should retrieve it based on the full path' do it 'should retrieve it based on the full path' do
@controller.request.fullpath = '/about_us/team.html' @controller.request.fullpath = '/about_us/team.html'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{about_us/team about_us/content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 2, :fullpath.in => %w{about_us/team about_us/content_type_template content_type_template/team}).returns([@page])
@controller.send(:locomotive_page).should_not be_nil @controller.send(:locomotive_page).should_not be_nil
end end
it 'does not include the query string' do it 'does not include the query string' do
@controller.request.fullpath = '/about_us/team.html?some=params&we=use' @controller.request.fullpath = '/about_us/team.html?some=params&we=use'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{about_us/team about_us/content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 2, :fullpath.in => %w{about_us/team about_us/content_type_template content_type_template/team}).returns([@page])
@controller.send(:locomotive_page).should_not be_nil @controller.send(:locomotive_page).should_not be_nil
end end
@ -118,7 +118,7 @@ describe 'Locomotive rendering system' do
@page.redirect = true @page.redirect = true
@page.redirect_url = 'http://www.example.com/' @page.redirect_url = 'http://www.example.com/'
@controller.request.fullpath = '/contact' @controller.request.fullpath = '/contact'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{contact content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 1, :fullpath.in => %w{contact content_type_template}).returns([@page])
end end
it 'redirects to the redirect_url' do it 'redirects to the redirect_url' do
@ -135,13 +135,15 @@ describe 'Locomotive rendering system' do
@content_entry = @content_type.entries.build(:_visible => true) @content_entry = @content_type.entries.build(:_visible => true)
@page.templatized = true @page.templatized = true
@page.stubs(:fetch_target_entry).returns(@content_entry) @page.stubs(:fetch_target_entry).returns(@content_entry)
@page.stubs(:fullpath).returns('/projects/content_type_template')
@controller.request.fullpath = '/projects/edeneo.html' @controller.request.fullpath = '/projects/edeneo.html'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{projects/edeneo projects/content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 2, :fullpath.in => %w{projects/edeneo projects/content_type_template content_type_template/edeneo}).returns([@page])
end end
it 'sets the content_entry variable' do it 'sets the content_entry variable' do
@controller.send(:locomotive_page).should_not be_nil page = @controller.send(:locomotive_page)
@controller.instance_variable_get(:@content_entry).should == @content_entry page.should_not be_nil
page.content_entry.should == @content_entry
end end
it 'returns the 404 page if the instance does not exist' do it 'returns the 404 page if the instance does not exist' do
@ -149,7 +151,6 @@ describe 'Locomotive rendering system' do
(klass = Locomotive::Page).expects(:published).returns([true]) (klass = Locomotive::Page).expects(:published).returns([true])
@controller.current_site.pages.expects(:not_found).returns(klass) @controller.current_site.pages.expects(:not_found).returns(klass)
@controller.send(:locomotive_page).should be_true @controller.send(:locomotive_page).should be_true
@controller.instance_variable_get(:@content_entry).should be_nil
end end
it 'returns the 404 page if the instance is not visible' do it 'returns the 404 page if the instance is not visible' do
@ -171,7 +172,7 @@ describe 'Locomotive rendering system' do
it 'should return the 404 page if the page has not been published yet' do it 'should return the 404 page if the page has not been published yet' do
@controller.request.fullpath = '/contact' @controller.request.fullpath = '/contact'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{contact content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 1, :fullpath.in => %w{contact content_type_template}).returns([@page])
(klass = Locomotive::Page).expects(:published).returns([true]) (klass = Locomotive::Page).expects(:published).returns([true])
@controller.current_site.pages.expects(:not_found).returns(klass) @controller.current_site.pages.expects(:not_found).returns(klass)
@controller.send(:locomotive_page).should be_true @controller.send(:locomotive_page).should be_true
@ -180,7 +181,7 @@ describe 'Locomotive rendering system' do
it 'should not return the 404 page if the page has not been published yet and admin is logged in' do it 'should not return the 404 page if the page has not been published yet and admin is logged in' do
@controller.current_locomotive_account = true @controller.current_locomotive_account = true
@controller.request.fullpath = '/contact' @controller.request.fullpath = '/contact'
@controller.current_site.pages.expects(:any_in).with({ :fullpath => %w{contact content_type_template} }).returns([@page]) @controller.current_site.pages.expects(:where).with(:depth => 1, :fullpath.in => %w{contact content_type_template}).returns([@page])
@controller.send(:locomotive_page).should == @page @controller.send(:locomotive_page).should == @page
end end

View File

@ -203,12 +203,30 @@ describe Locomotive::Page do
end end
describe 'render module' do
context '#path combinations' do
it 'generates them for a path depth equals to 1' do
Locomotive::Page.path_combinations('foo').should == ['foo', 'content_type_template']
end
it 'generates them for a path depth equals to 2' do
Locomotive::Page.path_combinations('foo/bar').should == ['foo/bar', 'foo/content_type_template', 'content_type_template/bar']
end
it 'generates them for a path depth equals to 3' do
Locomotive::Page.path_combinations('foo/bar/baz').should == ['foo/bar/baz', 'foo/bar/content_type_template', 'foo/content_type_template/baz', 'content_type_template/bar/baz']
end
end
end
describe 'templatized extension' do describe 'templatized extension' do
before(:each) do before(:each) do
@page = FactoryGirl.build(:page, :templatized => true, :target_klass_name => 'Foo') @page = FactoryGirl.build(:page, :parent => Factory.build(:page, :templatized => false), :templatized => true, :target_klass_name => 'Foo')
# @page.stubs(:target_klass)
# Locomotive::ContentType.stubs(:find).returns(FactoryGirl.build(:content_type, :site => nil))
end end
it 'is considered as a templatized page' do it 'is considered as a templatized page' do
@ -233,6 +251,44 @@ describe Locomotive::Page do
@page.fetch_target_entry('foo') @page.fetch_target_entry('foo')
end end
context '#descendants' do
before(:each) do
@home = FactoryGirl.create(:page)
@page.attributes = { :parent_id => @home._id, :site => @home.site }; @page.save!
@sub_page = FactoryGirl.build(:page, :title => 'Subpage', :slug => 'foo', :parent => @page, :site => @home.site, :templatized => false)
end
it 'inherits the templatized property from its parent' do
@sub_page.valid?
@sub_page.templatized?.should be_true
@sub_page.templatized_from_parent?.should be_true
@sub_page.target_klass_name.should == 'Foo'
end
it 'gets templatized if its parent is' do
@page.attributes = { :templatized => false, :target_klass_name => nil }; @page.save!
@sub_page.save.should be_true
@sub_page.templatized?.should be_false
@page.attributes = { :templatized => true, :target_klass_name => 'Foo' }; @page.save!
@sub_page.reload
@sub_page.templatized?.should be_true
@sub_page.templatized_from_parent?.should be_true
@sub_page.target_klass_name.should == 'Foo'
end
it 'is not templatized if its parent is no more a templatized page' do
@sub_page.save.should be_true
@page.templatized = false; @page.save!
@sub_page.reload
@sub_page.templatized.should be_false
@sub_page.templatized_from_parent.should be_false
@sub_page.target_klass_name.should be_nil
end
end
context 'using a content type' do context 'using a content type' do
before(:each) do before(:each) do