rendering engine + liquid tags/drops/filters + rspec tests + fix small bugs
This commit is contained in:
parent
311903a43d
commit
9447386f0e
10
Gemfile
10
Gemfile
@ -20,11 +20,11 @@ gem "rmagick"
|
||||
|
||||
# Development environment
|
||||
group :development do
|
||||
# Using mongrel instead of webrick (default server)
|
||||
# gem "mongrel"
|
||||
# gem "cgi_multipart_eof_fix"
|
||||
# gem "fastthread"
|
||||
# gem "mongrel_experimental"
|
||||
# Using mongrel instead of webrick (default server)
|
||||
gem "mongrel"
|
||||
gem "cgi_multipart_eof_fix"
|
||||
gem "fastthread"
|
||||
gem "mongrel_experimental"
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
@ -2,11 +2,12 @@ class PagesController < ActionController::Base
|
||||
|
||||
include Locomotive::Routing::SiteDispatcher
|
||||
|
||||
include Locomotive::Render
|
||||
|
||||
before_filter :require_site
|
||||
|
||||
def show
|
||||
logger.debug "fullpath = #{request.fullpath}"
|
||||
# @page = current_site.pages.find
|
||||
render_locomotive_page
|
||||
end
|
||||
|
||||
end
|
@ -17,6 +17,8 @@ class Asset
|
||||
## validations ##
|
||||
validates_presence_of :name, :source
|
||||
|
||||
## behaviours ##
|
||||
|
||||
## methods ##
|
||||
|
||||
%w{image stylesheet javascript pdf video audio}.each do |type|
|
||||
@ -24,5 +26,9 @@ class Asset
|
||||
self.content_type == type
|
||||
end
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
{ :url => self.source.url }.merge(self.attributes)
|
||||
end
|
||||
|
||||
end
|
@ -13,6 +13,7 @@ class AssetCollection
|
||||
|
||||
## behaviours ##
|
||||
custom_fields_for :assets
|
||||
liquid_methods :name, :ordered_assets
|
||||
|
||||
## callbacks ##
|
||||
before_validate :normalize_slug
|
||||
|
@ -28,9 +28,14 @@ class ContentType
|
||||
|
||||
## methods ##
|
||||
|
||||
def ordered_contents
|
||||
def ordered_contents(conditions = {})
|
||||
column = self.order_by.to_sym
|
||||
self.contents.sort { |a, b| (a.send(column) || 0) <=> (b.send(column) || 0) }
|
||||
|
||||
(if conditions.nil? || conditions.empty?
|
||||
self.contents
|
||||
else
|
||||
self.contents.where(conditions)
|
||||
end).sort { |a, b| (a.send(column) || 0) <=> (b.send(column) || 0) }
|
||||
end
|
||||
|
||||
def sort_contents!(order)
|
||||
|
@ -12,11 +12,13 @@ class Layout < LiquidTemplate
|
||||
validates_format_of :value, :with => Locomotive::Regexps::CONTENT_FOR_LAYOUT, :message => :missing_content_for_layout
|
||||
|
||||
## methods ##
|
||||
|
||||
|
||||
protected
|
||||
|
||||
def build_parts_from_value
|
||||
if self.value_changed? || self.new_record?
|
||||
self.parts.each { |p| p.disabled = true }
|
||||
|
||||
self.value.scan(Locomotive::Regexps::CONTENT_FOR).each do |attributes|
|
||||
slug = attributes[0].strip.downcase
|
||||
name = attributes[1].strip.gsub("\"", '')
|
||||
@ -25,6 +27,7 @@ class Layout < LiquidTemplate
|
||||
|
||||
if part = self.parts.detect { |p| p.slug == slug }
|
||||
part.name = name if name.present?
|
||||
part.disabled = false
|
||||
else
|
||||
self.parts.build :slug => slug, :name => name || slug
|
||||
end
|
||||
|
@ -6,16 +6,24 @@ class LiquidTemplate
|
||||
field :name
|
||||
field :slug
|
||||
field :value
|
||||
field :template, :type => Binary
|
||||
|
||||
## associations ##
|
||||
belongs_to_related :site
|
||||
|
||||
## callbacks ##
|
||||
before_validate :normalize_slug
|
||||
before_validate :store_template
|
||||
|
||||
## validations ##
|
||||
validates_presence_of :site, :name, :slug, :value
|
||||
validates_uniqueness_of :slug, :scope => :site_id #[:site_id, :_type]
|
||||
validates_uniqueness_of :slug, :scope => :site_id
|
||||
|
||||
## methods ##
|
||||
|
||||
def template
|
||||
Marshal.load(read_attribute(:template).to_s) rescue nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
@ -23,5 +31,14 @@ class LiquidTemplate
|
||||
self.slug = self.name.clone if self.slug.blank? && self.name.present?
|
||||
self.slug.slugify!(:without_extension => true, :downcase => true) if self.slug.present?
|
||||
end
|
||||
|
||||
def store_template
|
||||
begin
|
||||
parsed_template = Liquid::Template.parse(self.value)
|
||||
self.template = BSON::Binary.new(Marshal.dump(parsed_template))
|
||||
rescue Liquid::SyntaxError => error
|
||||
self.errors.add :value, :liquid_syntax_error
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -6,10 +6,12 @@ class Page
|
||||
## fields ##
|
||||
field :title
|
||||
field :slug
|
||||
field :fullpath
|
||||
field :published, :type => Boolean, :default => false
|
||||
field :keywords
|
||||
field :description
|
||||
field :position, :type => Integer
|
||||
field :template, :type => Binary
|
||||
|
||||
## associations ##
|
||||
belongs_to_related :site
|
||||
@ -19,6 +21,8 @@ class Page
|
||||
## callbacks ##
|
||||
before_validate :reset_parent
|
||||
before_validate :normalize_slug
|
||||
before_validate :store_template
|
||||
before_save { |p| p.fullpath = p.fullpath(true) }
|
||||
before_save { |p| p.parent_id = nil if p.parent_id.blank? }
|
||||
before_save :change_parent
|
||||
before_create { |p| p.parts << PagePart.build_body_part if p.parts.empty? }
|
||||
@ -34,6 +38,8 @@ class Page
|
||||
|
||||
## named scopes ##
|
||||
named_scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.lastest_items_nb
|
||||
named_scope :index, :where => { :slug => 'index', :depth => 0 }
|
||||
named_scope :not_found, :where => { :slug => '404', :depth => 0 }
|
||||
|
||||
## behaviours ##
|
||||
acts_as_tree :order => ['position', 'asc']
|
||||
@ -65,16 +71,27 @@ class Page
|
||||
child.save
|
||||
end
|
||||
end
|
||||
|
||||
def route
|
||||
return self.slug if self.index? || self.not_found?
|
||||
slugs = self.self_and_ancestors.map(&:slug)
|
||||
slugs.shift
|
||||
File.join slugs
|
||||
|
||||
def fullpath(force = false)
|
||||
if read_attribute(:fullpath).present? && !force
|
||||
return read_attribute(:fullpath)
|
||||
end
|
||||
|
||||
if self.index? || self.not_found?
|
||||
self.slug
|
||||
else
|
||||
slugs = self.self_and_ancestors.map(&:slug)
|
||||
slugs.shift
|
||||
File.join slugs
|
||||
end
|
||||
end
|
||||
|
||||
def url
|
||||
"http://#{self.site.domains.first}/#{self.route}.html"
|
||||
"http://#{self.site.domains.first}/#{self.fullpath}.html"
|
||||
end
|
||||
|
||||
def template
|
||||
Marshal.load(read_attribute(:template).to_s) rescue nil
|
||||
end
|
||||
|
||||
def ancestors
|
||||
@ -82,6 +99,16 @@ class Page
|
||||
self.class.find(self.path.clone << nil) # bug in mongoid (it does not handle array with one element)
|
||||
end
|
||||
|
||||
def render(context)
|
||||
self.template.render(context)
|
||||
|
||||
if self.layout
|
||||
self.layout.template.render(context)
|
||||
else
|
||||
Liquid::Template.parse("{{ content_for_layout }}").render(context)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def do_not_remove_index_and_404_pages
|
||||
@ -163,4 +190,14 @@ class Page
|
||||
self.slug = self.title.clone if self.slug.blank? && self.title.present?
|
||||
self.slug.slugify!(:without_extension => true) if self.slug.present?
|
||||
end
|
||||
|
||||
def store_template
|
||||
begin
|
||||
parsed_template = Liquid::Template.parse(self.parts.enabled.map(&:template).join(''))
|
||||
self.template = BSON::Binary.new(Marshal.dump(parsed_template))
|
||||
rescue Liquid::SyntaxError => error
|
||||
self.errors.add :template, :liquid_syntax_error
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -22,6 +22,10 @@ class PagePart
|
||||
|
||||
## methods ##
|
||||
|
||||
def template
|
||||
"{% capture content_for_#{self.slug} %}#{self.value}{% endcapture %}"
|
||||
end
|
||||
|
||||
def self.build_body_part
|
||||
self.new({
|
||||
:name => I18n.t('attributes.defaults.page_parts.name'),
|
||||
@ -29,4 +33,5 @@ class PagePart
|
||||
:slug => 'layout'
|
||||
})
|
||||
end
|
||||
|
||||
end
|
@ -33,6 +33,7 @@ class Site
|
||||
named_scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :_id.ne => site.id } } }
|
||||
|
||||
## behaviours ##
|
||||
liquid_methods :name
|
||||
|
||||
## methods ##
|
||||
|
||||
@ -56,7 +57,7 @@ class Site
|
||||
def domains_with_subdomain
|
||||
((self.domains || []) + ["#{self.subdomain}.#{Locomotive.config.default_domain}"]).uniq
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
def domains_must_be_valid_and_unique
|
||||
|
@ -3,8 +3,8 @@ class ThemeAsset
|
||||
include Mongoid::Timestamps
|
||||
|
||||
## fields ##
|
||||
field :slug, :type => String
|
||||
field :content_type, :type => String
|
||||
field :slug
|
||||
field :content_type
|
||||
field :width, :type => Integer
|
||||
field :height, :type => Integer
|
||||
field :size, :type => Integer
|
||||
@ -22,6 +22,7 @@ class ThemeAsset
|
||||
validate :extname_can_not_be_changed
|
||||
validates_presence_of :site, :source
|
||||
validates_presence_of :slug, :if => Proc.new { |a| a.new_record? && a.performing_plain_text? }
|
||||
validates_uniqueness_of :slug, :scope => [:site_id, :content_type]
|
||||
validates_integrity_of :source
|
||||
|
||||
## accessors ##
|
||||
@ -68,6 +69,10 @@ class ThemeAsset
|
||||
"#{self.slug}#{File.extname(self.source.file.original_filename)}"
|
||||
end
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
{ :url => self.source.url }.merge(self.attributes)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
- content_for :buttons do
|
||||
= admin_button_tag t('admin.theme_assets.index.new'), new_admin_theme_asset_url, :class => 'add'
|
||||
|
||||
%p= t('.help')
|
||||
%p= t('.help', :url => @asset.source.url)
|
||||
|
||||
= semantic_form_for @asset, :url => admin_theme_asset_url(@asset), :html => { :multipart => true } do |form|
|
||||
|
||||
|
@ -84,6 +84,8 @@ en:
|
||||
new: new snippet
|
||||
new:
|
||||
title: New snippet
|
||||
edit:
|
||||
title: Editing snippet
|
||||
|
||||
sites:
|
||||
new:
|
||||
@ -118,7 +120,8 @@ en:
|
||||
new:
|
||||
title: New file
|
||||
edit:
|
||||
title: "Editing {{file}}"
|
||||
title: "Editing {{file}}"
|
||||
help: "You can use it by copying/pasting the following url: {{url}}"
|
||||
form:
|
||||
choose_file: Choose file
|
||||
choose_plain_text: Choose plain text
|
||||
|
@ -8,6 +8,7 @@ en:
|
||||
protected_page: "You can not remove index or 404 pages"
|
||||
extname_changed: "New file does not have the original extension"
|
||||
array_too_short: "is too small (minimum element number is {{count}})"
|
||||
liquid_syntax_error: "Syntax error in page parts, please check the syntax"
|
||||
|
||||
attributes:
|
||||
defaults:
|
||||
@ -22,3 +23,7 @@ en:
|
||||
body: "Content goes here"
|
||||
page_parts:
|
||||
name: "Body"
|
||||
|
||||
pagination:
|
||||
previous: "« Previous"
|
||||
next: "Next »"
|
||||
|
15
doc/TODO
15
doc/TODO
@ -1,20 +1,24 @@
|
||||
BOARD:
|
||||
- liquid rendering engine
|
||||
- theme assets
|
||||
- theme assets picker (???)
|
||||
|
||||
BACKLOG:
|
||||
- theme assets
|
||||
- asset collections: custom resizing if image
|
||||
- file type (icons)
|
||||
|
||||
- devise messages in French
|
||||
- localize devise emails
|
||||
- refactoring admin crud (pages + layouts + snippets)
|
||||
- refactoring page.rb => create module pagetree
|
||||
|
||||
- refactoring: CustomFields::CustomField => CustomFields::Field
|
||||
- optimization custom_fields: use dynamic class for a collection instead of modifying the metaclass each time we build an item
|
||||
|
||||
BUGS:
|
||||
- theme assets: disable version if not image
|
||||
- assets uploader: remove old files if new one
|
||||
|
||||
NICE TO HAVE:
|
||||
- asset collections: custom resizing if image
|
||||
|
||||
DONE:
|
||||
x admin layout
|
||||
@ -78,4 +82,7 @@ x content types / models (CRUD)
|
||||
x pre-select the first custom field as the highlighted one
|
||||
x contents (CRUD)
|
||||
x sort contents
|
||||
x contents sub menu => BUG
|
||||
x contents sub menu => BUG
|
||||
x liquid rendering engine
|
||||
x contents pagination
|
||||
x how to disable a page part in layout ? (BUG)
|
@ -1,5 +1,6 @@
|
||||
# require 'locomotive/patches'
|
||||
require 'locomotive/configuration'
|
||||
require 'locomotive/liquid'
|
||||
|
||||
module Locomotive
|
||||
|
||||
|
5
lib/locomotive/liquid.rb
Normal file
5
lib/locomotive/liquid.rb
Normal file
@ -0,0 +1,5 @@
|
||||
%w{. tags drops filters}.each do |dir|
|
||||
Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }
|
||||
end
|
||||
|
||||
::Liquid::Template.file_system = Locomotive::Liquid::DbFileSystem.new # enable snippets
|
22
lib/locomotive/liquid/db_file_system.rb
Normal file
22
lib/locomotive/liquid/db_file_system.rb
Normal file
@ -0,0 +1,22 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
# Works only with snippets
|
||||
class DbFileSystem
|
||||
|
||||
def read_template_file(site, template_path)
|
||||
raise FileSystemError, "Illegal snippet name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
|
||||
|
||||
snippet = site.snippets.where(:slug => template_path).first
|
||||
|
||||
raise FileSystemError, "No such snippet '#{template_path}'" if snippet.nil?
|
||||
|
||||
snippet.template
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
23
lib/locomotive/liquid/drops/asset_collections.rb
Normal file
23
lib/locomotive/liquid/drops/asset_collections.rb
Normal file
@ -0,0 +1,23 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class AssetCollections < ::Liquid::Drop
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
|
||||
def before_method(meth)
|
||||
@site.asset_collections.where(:slug => meth.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
55
lib/locomotive/liquid/drops/base.rb
Normal file
55
lib/locomotive/liquid/drops/base.rb
Normal file
@ -0,0 +1,55 @@
|
||||
# Code taken from Mephisto sources (http://mephistoblog.com/)
|
||||
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class Base < ::Liquid::Drop
|
||||
class_inheritable_reader :liquid_attributes
|
||||
write_inheritable_attribute :liquid_attributes, []
|
||||
attr_reader :source
|
||||
delegate :hash, :to => :source
|
||||
|
||||
def initialize(source)
|
||||
unless source.nil?
|
||||
@source = source
|
||||
@liquid = liquid_attributes.flatten.inject({}) { |h, k| h.update k.to_s => @source.send(k) }
|
||||
end
|
||||
end
|
||||
|
||||
def id
|
||||
(@source.respond_to?(:id) ? @source.id : nil) || 'new'
|
||||
end
|
||||
|
||||
def before_method(method)
|
||||
@liquid[method.to_s]
|
||||
end
|
||||
|
||||
# converts an array of records to an array of liquid drops
|
||||
def self.liquify(*records, &block)
|
||||
i = -1
|
||||
records =
|
||||
records.inject [] do |all, r|
|
||||
i+=1
|
||||
attrs = (block && block.arity == 1) ? [r] : [r, i]
|
||||
all << (block ? block.call(*attrs) : r.to_liquid)
|
||||
all
|
||||
end
|
||||
records.compact!
|
||||
records
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def liquify(*records, &block)
|
||||
self.class.liquify(*records, &block)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
25
lib/locomotive/liquid/drops/content.rb
Normal file
25
lib/locomotive/liquid/drops/content.rb
Normal file
@ -0,0 +1,25 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class Content < Base
|
||||
|
||||
@@forbidden_attributes = %w{_id _version _index}
|
||||
|
||||
def before_method(meth)
|
||||
return '' if @source.nil?
|
||||
|
||||
if not @@forbidden_attributes.include?(meth.to_s)
|
||||
@source.send(meth)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
70
lib/locomotive/liquid/drops/contents.rb
Normal file
70
lib/locomotive/liquid/drops/contents.rb
Normal file
@ -0,0 +1,70 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class Contents < ::Liquid::Drop
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
|
||||
def before_method(meth)
|
||||
type = @site.content_types.where(:slug => meth.to_s).first
|
||||
ProxyCollection.new(@site, type)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ProxyCollection < ::Liquid::Drop
|
||||
|
||||
def initialize(site, content_type)
|
||||
@site = site
|
||||
@content_type = content_type
|
||||
@collection = nil
|
||||
end
|
||||
|
||||
def first
|
||||
content = @content_type.ordered_contents(@context['with_scope']).first
|
||||
build_content_drop(content) unless content.nil?
|
||||
end
|
||||
|
||||
def last
|
||||
content = @content_type.ordered_contents(@context['with_scope']).last
|
||||
build_content_drop(content) unless content.nil?
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@collection ||= @content_type.ordered_contents(@context['with_scope'])
|
||||
to_content_drops.each(&block)
|
||||
end
|
||||
|
||||
def to_content_drops
|
||||
@collection.map { |c| build_content_drop(c) }
|
||||
end
|
||||
|
||||
def build_content_drop(content)
|
||||
Locomotive::Liquid::Drops::Content.new(content)
|
||||
end
|
||||
|
||||
def paginate(options = {})
|
||||
@collection ||= @content_type.ordered_contents(@context['with_scope']).paginate(options)
|
||||
{
|
||||
:collection => to_content_drops,
|
||||
:current_page => @collection.current_page,
|
||||
:previous_page => @collection.previous_page,
|
||||
:next_page => @collection.next_page,
|
||||
:total_entries => @collection.total_entries,
|
||||
:total_pages => @collection.total_pages,
|
||||
:per_page => @collection.per_page
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
24
lib/locomotive/liquid/drops/javascripts.rb
Normal file
24
lib/locomotive/liquid/drops/javascripts.rb
Normal file
@ -0,0 +1,24 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class Javascripts < ::Liquid::Drop
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
|
||||
def before_method(meth)
|
||||
asset = site.theme_assets.where(:content_type => 'javascript', :slug => meth.to_s).first
|
||||
!asset.nil? ? asset.source.url : nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
24
lib/locomotive/liquid/drops/stylesheets.rb
Normal file
24
lib/locomotive/liquid/drops/stylesheets.rb
Normal file
@ -0,0 +1,24 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Drops
|
||||
|
||||
class Stylesheets < ::Liquid::Drop
|
||||
|
||||
def initialize(site)
|
||||
@site = site
|
||||
end
|
||||
|
||||
def before_method(meth)
|
||||
asset = @site.theme_assets.where(:content_type => 'stylesheet', :slug => meth.to_s).first
|
||||
!asset.nil? ? asset.source.url : nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
36
lib/locomotive/liquid/filters/date.rb
Normal file
36
lib/locomotive/liquid/filters/date.rb
Normal file
@ -0,0 +1,36 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Filters
|
||||
|
||||
module Date
|
||||
|
||||
def localized_date(input, *args)
|
||||
format, locale = args[0], args[1] rescue 'en'
|
||||
|
||||
date = input.is_a?(String) ? Time.parse(input) : input
|
||||
|
||||
if format.to_s.empty?
|
||||
return input.to_s
|
||||
end
|
||||
|
||||
date = input.is_a?(String) ? Time.parse(input) : input
|
||||
|
||||
if date.respond_to?(:strftime)
|
||||
I18n.locale = locale
|
||||
I18n.l date, :format => format
|
||||
else
|
||||
input
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
::Liquid::Template.register_filter(Date)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
118
lib/locomotive/liquid/filters/html.rb
Normal file
118
lib/locomotive/liquid/filters/html.rb
Normal file
@ -0,0 +1,118 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Filters
|
||||
|
||||
module Html
|
||||
|
||||
# Write the link to a stylesheet resource
|
||||
# input: url of the css file
|
||||
def stylesheet_tag(input)
|
||||
return '' if input.nil?
|
||||
input = "#{input}.css" unless input.ends_with?('.css')
|
||||
%{<link href="#{input}" media="screen" rel="stylesheet" type="text/css" />}
|
||||
end
|
||||
|
||||
# Write the link to javascript resource
|
||||
# input: url of the javascript file
|
||||
def javascript_tag(input)
|
||||
return '' if input.nil?
|
||||
input = "#{input}.js" unless input.ends_with?('.js')
|
||||
%{<script src="#{input}" type="text/javascript"></script>}
|
||||
end
|
||||
|
||||
|
||||
# Write an image tag
|
||||
# input: url of the image OR asset drop
|
||||
def image_tag(input, *args)
|
||||
image_options = inline_options(args_to_options(args))
|
||||
"<img src=\"#{File.join('/', get_path_from_asset(input))}\" #{image_options}/>"
|
||||
end
|
||||
|
||||
# Embed a flash movie into a page
|
||||
# input: url of the flash movie OR asset drop
|
||||
# width: width (in pixel or in %) of the embedded movie
|
||||
# height: height (in pixel or in %) of the embedded movie
|
||||
def flash_tag(input, *args)
|
||||
path = get_path_from_asset(input)
|
||||
embed_options = inline_options(args_to_options(args))
|
||||
%{
|
||||
<object #{embed_options}>
|
||||
<param name="movie" value="#{path}" />
|
||||
<embed src="#{path}" #{embed_options}/>
|
||||
</embed>
|
||||
</object>
|
||||
}.gsub(/ >/, '>').strip
|
||||
end
|
||||
|
||||
# Render the navigation for a paginated collection
|
||||
def default_pagination(paginate, *args)
|
||||
return '' if paginate['parts'].empty?
|
||||
|
||||
options = args_to_options(args)
|
||||
|
||||
previous_link = (if paginate['previous'].blank?
|
||||
"<span class=\"disabled prev_page\">#{I18n.t('pagination.previous')}</span>"
|
||||
else
|
||||
"<a href=\"#{paginate['previous']['url']}\" class=\"prev_page\">#{I18n.t('pagination.previous')}</a>"
|
||||
end)
|
||||
|
||||
links = ""
|
||||
paginate['parts'].each do |part|
|
||||
links << (if part['is_link']
|
||||
"<a href=\"#{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\">#{I18n.t('pagination.next')}</span>"
|
||||
else
|
||||
"<a href=\"#{paginate['next']['url']}\" class=\"next_page\">#{I18n.t('pagination.next')}</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.to_a.collect { |a, b| "#{a}=\"#{b}\"" }).join(' ') << ' '
|
||||
end
|
||||
|
||||
# Get the path to be used in html tags such as image_tag, flash_tag, ...etc
|
||||
# input: url (String) OR asset drop
|
||||
def get_path_from_asset(input)
|
||||
input.respond_to?(:url) ? input.url : input
|
||||
end
|
||||
end
|
||||
|
||||
::Liquid::Template.register_filter(Html)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
35
lib/locomotive/liquid/filters/misc.rb
Normal file
35
lib/locomotive/liquid/filters/misc.rb
Normal file
@ -0,0 +1,35 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Filters
|
||||
|
||||
module Misc
|
||||
|
||||
def underscore(input)
|
||||
input.to_s.gsub(' ', '_').gsub('/', '_').underscore
|
||||
end
|
||||
|
||||
def dasherize(input)
|
||||
input.to_s.gsub(' ', '-').gsub('/', '-').dasherize
|
||||
end
|
||||
|
||||
def concat(input, *args)
|
||||
result = input.to_s
|
||||
args.flatten.each { |a| result << a.to_s }
|
||||
result
|
||||
end
|
||||
|
||||
def modulo(word, index, modulo)
|
||||
(index.to_i + 1) % modulo == 0 ? word : ''
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
::Liquid::Template.register_filter(Misc)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
27
lib/locomotive/liquid/tags/blueprint.rb
Normal file
27
lib/locomotive/liquid/tags/blueprint.rb
Normal file
@ -0,0 +1,27 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Tags
|
||||
|
||||
class Blueprint < ::Liquid::Tag
|
||||
|
||||
def render(context)
|
||||
%{
|
||||
<link href="/stylesheets/blueprint/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/blueprint/print.css" media="print" rel="stylesheet" type="text/css" />
|
||||
<!--[if IE]>
|
||||
<link href="/stylesheets/blueprint/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
||||
<![endif]-->
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('blueprint_stylesheets', Blueprint)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
23
lib/locomotive/liquid/tags/jquery.rb
Normal file
23
lib/locomotive/liquid/tags/jquery.rb
Normal file
@ -0,0 +1,23 @@
|
||||
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
|
100
lib/locomotive/liquid/tags/paginate.rb
Normal file
100
lib/locomotive/liquid/tags/paginate.rb
Normal file
@ -0,0 +1,100 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Tags
|
||||
|
||||
# Paginate a collection
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# {% paginate contents.projects by 5 %}
|
||||
# {% for project in paginate.collection %}
|
||||
# {{ project.name }}
|
||||
# {% endfor %}
|
||||
# {% endpaginate %}
|
||||
#
|
||||
|
||||
class Paginate < ::Liquid::Block
|
||||
|
||||
Syntax = /(#{::Liquid::Expression}+)\s+by\s+([0-9]+)/
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
if markup =~ Syntax
|
||||
@collection_name = $1
|
||||
@per_page = $2.to_i
|
||||
else
|
||||
raise SyntaxError.new("Syntax Error in 'paginate' - Valid syntax: paginate [collection] by [number]")
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
context.stack do
|
||||
collection = context[@collection_name]
|
||||
|
||||
raise ArgumentError.new("Cannot paginate array '#{@collection_name}'. Not found.") if collection.nil?
|
||||
|
||||
pagination = collection.paginate({
|
||||
:page => context['current_page'],
|
||||
:per_page => @per_page }).stringify_keys!
|
||||
|
||||
page_count, current_page = pagination['total_pages'], pagination['current_page']
|
||||
|
||||
path = context['page'].path rescue '/'
|
||||
|
||||
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['parts'] = []
|
||||
|
||||
hellip_break = false
|
||||
|
||||
if page_count > 1
|
||||
1.upto(page_count) do |page|
|
||||
if current_page == page
|
||||
pagination['parts'] << no_link(page)
|
||||
elsif page == 1
|
||||
pagination['parts'] << link(page, page, path)
|
||||
elsif page == page_count - 1
|
||||
pagination['parts'] << link(page, page, path)
|
||||
elsif page <= current_page - window_size or page >= current_page + window_size
|
||||
next if hellip_break
|
||||
pagination['parts'] << no_link('…')
|
||||
hellip_break = true
|
||||
next
|
||||
else
|
||||
pagination['parts'] << link(page, page, path)
|
||||
end
|
||||
|
||||
hellip_break = false
|
||||
end
|
||||
end
|
||||
|
||||
context['paginate'] = pagination
|
||||
|
||||
render_all(@nodelist, context)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def window_size
|
||||
3
|
||||
end
|
||||
|
||||
def no_link(title)
|
||||
{ 'title' => title, 'is_link' => false, 'hellip_break' => title == '…' }
|
||||
end
|
||||
|
||||
def link(title, page, path)
|
||||
{ 'title' => title, 'url' => path + "?page=#{page}", 'is_link' => true}
|
||||
end
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('paginate', Paginate)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
42
lib/locomotive/liquid/tags/snippet.rb
Normal file
42
lib/locomotive/liquid/tags/snippet.rb
Normal file
@ -0,0 +1,42 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Tags
|
||||
|
||||
class Snippet < ::Liquid::Include
|
||||
|
||||
def render(context)
|
||||
site = context.registers[:site]
|
||||
|
||||
partial = ::Liquid::Template.file_system.read_template_file(site, context[@template_name])
|
||||
|
||||
variable = context[@variable_name || @template_name[1..-2]]
|
||||
|
||||
context.stack do
|
||||
@attributes.each do |key, value|
|
||||
context[key] = context[value]
|
||||
end
|
||||
|
||||
output = (if variable.is_a?(Array)
|
||||
variable.collect do |variable|
|
||||
context[@template_name[1..-2]] = variable
|
||||
partial.render(context)
|
||||
end
|
||||
else
|
||||
context[@template_name[1..-2]] = variable
|
||||
partial.render(context)
|
||||
end)
|
||||
|
||||
output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('include', Snippet)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
45
lib/locomotive/liquid/tags/with_scope.rb
Normal file
45
lib/locomotive/liquid/tags/with_scope.rb
Normal file
@ -0,0 +1,45 @@
|
||||
module Locomotive
|
||||
|
||||
module Liquid
|
||||
|
||||
module Tags
|
||||
|
||||
class WithScope < ::Liquid::Block
|
||||
|
||||
def initialize(tag_name, markup, tokens)
|
||||
@attributes = {}
|
||||
markup.scan(::Liquid::TagAttributes) do |key, value|
|
||||
@attributes[key] = value
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def render(context)
|
||||
context.stack do
|
||||
context['with_scope'] = decode(@attributes)
|
||||
render_all(@nodelist, context)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def decode(attributes)
|
||||
attributes.each_pair do |key, value|
|
||||
attributes[key] = (case value
|
||||
when /true|false/ then value == 'true'
|
||||
when /[0-9]+/ then value.to_i
|
||||
when /'(\S+)'/ then $1
|
||||
else
|
||||
value
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
::Liquid::Template.register_tag('with_scope', WithScope)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
54
lib/locomotive/render.rb
Normal file
54
lib/locomotive/render.rb
Normal file
@ -0,0 +1,54 @@
|
||||
module Locomotive
|
||||
module Render
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
protected
|
||||
|
||||
def render_locomotive_page
|
||||
page = locomotive_page
|
||||
|
||||
redirect_to application_root_url and return if page.nil?
|
||||
|
||||
output = page.render(locomotive_context)
|
||||
|
||||
prepare_and_set_response(output)
|
||||
end
|
||||
|
||||
def locomotive_page
|
||||
path = request.fullpath.clone
|
||||
path.gsub!(/\.[a-zA-Z][a-zA-Z0-9]{2,}$/, '')
|
||||
path.gsub!(/^\//, '')
|
||||
path = 'index' if path.blank?
|
||||
|
||||
current_site.pages.where(:fullpath => path).first ||
|
||||
current_site.pages.not_found.first
|
||||
end
|
||||
|
||||
def locomotive_context
|
||||
assigns = {
|
||||
'site' => current_site,
|
||||
'asset_collections' => Locomotive::Liquid::Drops::AssetCollections.new(current_site),
|
||||
# 'theme_assets' => Locomotive::Liquid::Drops::ThemeAssets.new(current_site),
|
||||
'stylesheets' => Locomotive::Liquid::Drops::Stylesheets.new(current_site),
|
||||
'javascripts' => Locomotive::Liquid::Drops::Javascripts.new(current_site),
|
||||
'contents' => Locomotive::Liquid::Drops::Contents.new(current_site),
|
||||
'current_page' => self.params[:page]
|
||||
}
|
||||
|
||||
registers = { :controller => self, :site => current_site }
|
||||
|
||||
::Liquid::Context.new(assigns, registers)
|
||||
end
|
||||
|
||||
def prepare_and_set_response(output)
|
||||
response.headers["Content-Type"] = 'text/html; charset=utf-8'
|
||||
render :text => output, :layout => false, :status => :ok
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,82 +0,0 @@
|
||||
module Locomotive
|
||||
|
||||
class Sitemap
|
||||
|
||||
attr_accessor :index, :children, :not_found
|
||||
|
||||
def initialize(site)
|
||||
self.children = site.pages.roots.to_a
|
||||
|
||||
self.index = self.children.detect { |p| p.index? }
|
||||
self.not_found = self.children.detect { |p| p.not_found? }
|
||||
|
||||
self.children.delete(self.index)
|
||||
self.children.delete(self.not_found)
|
||||
end
|
||||
|
||||
def empty?
|
||||
self.index.nil? && self.not_found.nil? && self.children.empty?
|
||||
end
|
||||
|
||||
def self.build(site); self.new(site); end
|
||||
|
||||
end
|
||||
|
||||
# module Sitemap
|
||||
#
|
||||
# def self.build(pages)
|
||||
# return [] if pages.empty?
|
||||
#
|
||||
# # pages = pages.to_a.clone # make a secure copy
|
||||
# dictionary = build_dictionary(pages)
|
||||
#
|
||||
# returning [] do |map|
|
||||
# map << (index = pages.detect { |p| p.index? })
|
||||
# pages.delete(index)
|
||||
#
|
||||
# not_found = pages.detect { |p| p.not_found? }
|
||||
# pages.delete(not_found)
|
||||
#
|
||||
# add_children()
|
||||
#
|
||||
# map << not_found
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# protected
|
||||
#
|
||||
# def self.add_children(map, children, dictionary)
|
||||
#
|
||||
# end
|
||||
#
|
||||
# def self.build_dictionary(pages)
|
||||
# returning({}) do |hash|
|
||||
# hash[pages.id] = pages
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # def self.build(pages)
|
||||
# # return Folder.new if pages.empty?
|
||||
# #
|
||||
# # pages = pages.to_a.clone # make a secure copy
|
||||
# #
|
||||
# # root = Folder.new :root => pages.delete_if { |p| p.path == 'index' }
|
||||
# #
|
||||
# # pages.each do |page|
|
||||
# #
|
||||
# # end
|
||||
# # end
|
||||
# #
|
||||
# # class Folder
|
||||
# # attr_accessor :root, :depth, :children
|
||||
# #
|
||||
# # def initialize(attributes = {})
|
||||
# # self.root = attributes[:root]
|
||||
# # self.depth = self.root.depth
|
||||
# # end
|
||||
# #
|
||||
# # end
|
||||
#
|
||||
# end
|
||||
|
||||
end
|
@ -17,16 +17,16 @@ $(document).ready(function() {
|
||||
$('#parts code textarea').each(function() { addCodeMirrorEditor('liquid', $(this)); });
|
||||
|
||||
var refreshParts = function(parts) {
|
||||
console.log('refreshParts');
|
||||
// console.log('refreshParts');
|
||||
$('#page-parts .nav a').removeClass('enabled');
|
||||
|
||||
$(parts).each(function() {
|
||||
console.log("iterating..." + this.slug);
|
||||
// console.log("iterating..." + this.slug);
|
||||
var control = $('#control-part-' + this.slug);
|
||||
|
||||
// adding missing part
|
||||
if (control.size() == 0) {
|
||||
console.log('adding part');
|
||||
// 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>');
|
||||
|
||||
@ -75,7 +75,7 @@ $(document).ready(function() {
|
||||
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');
|
||||
$.get(url, '', function(data) { refreshParts(data.parts); }, 'json');
|
||||
}
|
||||
|
||||
$('#page_layout_id').change(loadPartsFromLayout);
|
||||
|
@ -55,7 +55,7 @@ Factory.define :snippet do |s|
|
||||
s.association :site, :factory => :site
|
||||
s.name 'My website title'
|
||||
s.slug 'header'
|
||||
s.value %{<title>Acme</title}
|
||||
s.value %{<title>Acme</title>}
|
||||
end
|
||||
|
||||
## Theme assets ##
|
||||
|
82
spec/lib/locomotive/liquid/filters/html_spec.rb
Normal file
82
spec/lib/locomotive/liquid/filters/html_spec.rb
Normal file
@ -0,0 +1,82 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Locomotive::Liquid::Filters::Html do
|
||||
|
||||
include Locomotive::Liquid::Filters::Html
|
||||
|
||||
it 'should return a link tag for a stylesheet file' do
|
||||
result = "<link href=\"main.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />"
|
||||
stylesheet_tag('main.css').should == result
|
||||
stylesheet_tag('main').should == result
|
||||
stylesheet_tag(nil).should == ''
|
||||
end
|
||||
|
||||
it 'should return a script tag for a javascript file' do
|
||||
result = %{<script src="main.js" type="text/javascript"></script>}
|
||||
javascript_tag('main.js').should == result
|
||||
javascript_tag('main').should == result
|
||||
javascript_tag(nil).should == ''
|
||||
end
|
||||
|
||||
it 'should return an image tag without paramaters' do
|
||||
image_tag('foo.jpg').should == "<img src=\"/foo.jpg\" />"
|
||||
end
|
||||
|
||||
it 'should return an image tag with size' do
|
||||
image_tag('foo.jpg', 'width:100', 'height:50').should == "<img src=\"/foo.jpg\" height=\"50\" width=\"100\" />"
|
||||
end
|
||||
|
||||
it 'should return a flash tag without parameters' do
|
||||
flash_tag('foo.flv').should == %{
|
||||
<object>
|
||||
<param name="movie" value="foo.flv" />
|
||||
<embed src="foo.flv" />
|
||||
</embed>
|
||||
</object>
|
||||
}.strip
|
||||
end
|
||||
|
||||
it 'should return a flash tag with size' do
|
||||
flash_tag('foo.flv', 'width:100', 'height:50').should == %{
|
||||
<object height=\"50\" width=\"100\">
|
||||
<param name="movie" value="foo.flv" />
|
||||
<embed src="foo.flv" height=\"50\" width=\"100\" />
|
||||
</embed>
|
||||
</object>
|
||||
}.strip
|
||||
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' => '…', '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">« Previous<\/span>/)
|
||||
html.should match(/<a href="\/\?page=2">2<\/a>/)
|
||||
html.should match(/<span class=\"gap\">\…<\/span>/)
|
||||
html.should match(/<a href="\/\?page=2" class="next_page">Next »<\/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">« Previous<\/span>/)
|
||||
html.should match(/<a href="\/\?page=4" class="prev_page">« Previous<\/a>/)
|
||||
html.should match(/<span class="disabled next_page">Next »<\/span>/)
|
||||
|
||||
pagination.merge!({ 'parts' => [] })
|
||||
html = default_pagination(pagination, 'css:flickr_pagination')
|
||||
html.should == ''
|
||||
end
|
||||
|
||||
end
|
36
spec/lib/locomotive/liquid/filters/misc_spec.rb
Normal file
36
spec/lib/locomotive/liquid/filters/misc_spec.rb
Normal file
@ -0,0 +1,36 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Locomotive::Liquid::Filters::Misc do
|
||||
|
||||
include Locomotive::Liquid::Filters::Misc
|
||||
|
||||
it 'should underscore 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 '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', 1, 3).should == ''
|
||||
modulo('foo', 2, 3).should == 'foo'
|
||||
modulo('foo', 3, 3).should == ''
|
||||
modulo('foo', 4, 3).should == ''
|
||||
modulo('foo', 5, 3).should == 'foo'
|
||||
end
|
||||
|
||||
end
|
99
spec/lib/locomotive/liquid/tags/paginate_spec.rb
Normal file
99
spec/lib/locomotive/liquid/tags/paginate_spec.rb
Normal file
@ -0,0 +1,99 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Locomotive::Liquid::Tags::Paginate do
|
||||
|
||||
it 'should have a valid syntax' do
|
||||
markup = "contents.projects by 5"
|
||||
lambda do
|
||||
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"])
|
||||
end.should_not raise_error
|
||||
end
|
||||
|
||||
it 'should raise an error if the syntax is incorrect' do
|
||||
["contents.projects by a", "contents.projects", "contents.projects 5"].each do |markup|
|
||||
lambda do
|
||||
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"])
|
||||
end.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it 'should paginate the collection' do
|
||||
template = Liquid::Template.parse(default_template)
|
||||
text = template.render!(liquid_context)
|
||||
|
||||
text.should match /!Ruby on Rails!/
|
||||
text.should match /!jQuery!/
|
||||
text.should_not match /!mongodb!/
|
||||
|
||||
text = template.render!(liquid_context(:page => 2))
|
||||
|
||||
text.should_not match /!jQuery!/
|
||||
text.should match /!mongodb!/
|
||||
text.should match /!Liquid!/
|
||||
text.should_not match /!sqlite3!/
|
||||
end
|
||||
|
||||
it 'should not paginate if collection is nil or empty' do
|
||||
template = Liquid::Template.parse(default_template)
|
||||
|
||||
lambda do
|
||||
template.render!(liquid_context(:collection => nil))
|
||||
end.should raise_error
|
||||
|
||||
lambda do
|
||||
template.render!(liquid_context(:collection => PaginatedCollection.new))
|
||||
end.should raise_error
|
||||
end
|
||||
|
||||
# ___ helpers methods ___ #
|
||||
|
||||
def liquid_context(options = {})
|
||||
::Liquid::Context.new({
|
||||
'projects' => options.has_key?(:collection) ? options[:collection] : PaginatedCollection.new(['Ruby on Rails', 'jQuery', 'mongodb', 'Liquid', 'sqlite3']),
|
||||
'current_page' => options[:page] || 1
|
||||
}, {}, true)
|
||||
end
|
||||
|
||||
def default_template
|
||||
"{% paginate projects by 2 %}
|
||||
{% for project in paginate.collection %}
|
||||
!{{ project }}!
|
||||
{% endfor %}
|
||||
{% endpaginate %}"
|
||||
end
|
||||
|
||||
class PaginatedCollection
|
||||
|
||||
def initialize(collection)
|
||||
@collection = collection || []
|
||||
end
|
||||
|
||||
def paginate(options = {})
|
||||
total_pages = (@collection.size.to_f / options[:per_page].to_f).to_f.ceil + 1
|
||||
offset = (options[:page] - 1) * options[:per_page]
|
||||
|
||||
{
|
||||
:collection => @collection[offset..(offset + options[:per_page]) - 1],
|
||||
:current_page => options[:page],
|
||||
:previous_page => options[:page] == 1 ? 1 : options[:page] - 1,
|
||||
:next_page => options[:page] == total_pages ? total_pages : options[:page] + 1,
|
||||
:total_entries => @collection.size,
|
||||
:total_pages => total_pages,
|
||||
:per_page => options[:per_page]
|
||||
}
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
@collection.each(&block)
|
||||
end
|
||||
|
||||
def method_missing(method, *args)
|
||||
@collection.send(method, *args)
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
end
|
20
spec/lib/locomotive/liquid/tags/snippet_spec.rb
Normal file
20
spec/lib/locomotive/liquid/tags/snippet_spec.rb
Normal file
@ -0,0 +1,20 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Locomotive::Liquid::Tags::Snippet do
|
||||
|
||||
before(:each) do
|
||||
Site.any_instance.stubs(:create_default_pages!).returns(true)
|
||||
site = Factory.build(:site)
|
||||
snippet = Factory.build(:snippet, :site => site)
|
||||
snippet.send(:store_template)
|
||||
site.snippets.stubs(:where).returns([snippet])
|
||||
@context = ::Liquid::Context.new({}, { :site => site })
|
||||
end
|
||||
|
||||
it 'should render it' do
|
||||
template = ::Liquid::Template.parse("{% include 'header' %}")
|
||||
text = template.render(@context)
|
||||
text.should == "<title>Acme</title>"
|
||||
end
|
||||
|
||||
end
|
20
spec/lib/locomotive/liquid/tags/with_scope_spec.rb
Normal file
20
spec/lib/locomotive/liquid/tags/with_scope_spec.rb
Normal file
@ -0,0 +1,20 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Locomotive::Liquid::Tags::WithScope do
|
||||
|
||||
it 'should decode options (boolean, interger, ...)' do
|
||||
scope = Locomotive::Liquid::Tags::WithScope.new('with_scope', 'active:true price:42 title:\'foo\' hidden:false', ["{% endwith_scope %}"])
|
||||
attributes = scope.send(:decode, scope.instance_variable_get(:@attributes))
|
||||
attributes['active'].should == true
|
||||
attributes['price'].should == 42
|
||||
attributes['title'].should == 'foo'
|
||||
attributes['hidden'].should == false
|
||||
end
|
||||
|
||||
it 'should store attributes in the context' do
|
||||
template = ::Liquid::Template.parse("{% with_scope active:true title:'foo' %}{{ with_scope.active }}-{{ with_scope.title }}{% endwith_scope %}")
|
||||
text = template.render
|
||||
text.should == "true-foo"
|
||||
end
|
||||
|
||||
end
|
57
spec/lib/locomotive/render_spec.rb
Normal file
57
spec/lib/locomotive/render_spec.rb
Normal file
@ -0,0 +1,57 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Locomotive rendering system' do
|
||||
|
||||
before(:each) do
|
||||
@controller = Locomotive::TestController.new
|
||||
Site.any_instance.stubs(:create_default_pages!).returns(true)
|
||||
@site = Factory.build(:site)
|
||||
Site.stubs(:find).returns(@site)
|
||||
@controller.current_site = @site
|
||||
end
|
||||
|
||||
context 'setting the response' do
|
||||
|
||||
before(:each) do
|
||||
@controller.send(:prepare_and_set_response, 'Hello world !')
|
||||
end
|
||||
|
||||
it 'should have a html content type' do
|
||||
@controller.response.headers["Content-Type"].should == 'text/html; charset=utf-8'
|
||||
end
|
||||
|
||||
it 'should display the output' do
|
||||
@controller.output.should == 'Hello world !'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'when retrieving page' do
|
||||
|
||||
it 'should retrieve the index page /' do
|
||||
@controller.request.fullpath = '/'
|
||||
@controller.current_site.pages.expects(:where).with({ :fullpath => 'index' }).returns([true])
|
||||
@controller.send(:locomotive_page).should be_true
|
||||
end
|
||||
|
||||
it 'should also retrieve the index page (index.html)' do
|
||||
@controller.request.fullpath = '/index.html'
|
||||
@controller.current_site.pages.expects(:where).with({ :fullpath => 'index' }).returns([true])
|
||||
@controller.send(:locomotive_page).should be_true
|
||||
end
|
||||
|
||||
it 'should retrieve it based on the full path' do
|
||||
@controller.request.fullpath = '/about_us/team.html'
|
||||
@controller.current_site.pages.expects(:where).with({ :fullpath => 'about_us/team' }).returns([true])
|
||||
@controller.send(:locomotive_page).should be_true
|
||||
end
|
||||
|
||||
it 'should return the 404 page if the page does not exist' do
|
||||
@controller.request.fullpath = '/contact'
|
||||
@controller.current_site.pages.expects(:not_found).returns([true])
|
||||
@controller.send(:locomotive_page).should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -14,7 +14,7 @@ describe Layout do
|
||||
layout.errors[:value].should == ["should contain 'content_for_layout' liquid tag"]
|
||||
end
|
||||
|
||||
describe 'page parts' do
|
||||
context 'dealing with page parts' do
|
||||
|
||||
before(:each) do
|
||||
@layout = Factory.build(:layout)
|
||||
@ -49,4 +49,16 @@ describe Layout do
|
||||
|
||||
end
|
||||
|
||||
context 'parsing liquid template' do
|
||||
|
||||
before(:each) do
|
||||
@layout = Factory.build(:layout)
|
||||
end
|
||||
|
||||
it 'should not raise an error if template is empty' do
|
||||
@layout.template.should be_nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -248,15 +248,15 @@ describe Page do
|
||||
archives.children.last.children.first.depth.should == 3
|
||||
end
|
||||
|
||||
it 'should generate a route / url from parents' do
|
||||
@home.route.should == 'index'
|
||||
it 'should generate a path / url from parents' do
|
||||
@home.fullpath.should == 'index'
|
||||
@home.url.should == 'http://acme.example.com/index.html'
|
||||
|
||||
@child_1.route.should == 'foo'
|
||||
@child_1.fullpath.should == 'foo'
|
||||
@child_1.url.should == 'http://acme.example.com/foo.html'
|
||||
|
||||
nested_page = Factory(:page, :title => 'Sub sub page 1', :slug => 'bar', :parent => @child_1, :site => @home.site)
|
||||
nested_page.route.should == 'foo/bar'
|
||||
nested_page.fullpath.should == 'foo/bar'
|
||||
nested_page.url.should == 'http://acme.example.com/foo/bar.html'
|
||||
end
|
||||
|
||||
@ -286,5 +286,44 @@ describe Page do
|
||||
[@child_1, @child_3.reload].each_with_index { |c, i| c.position.should == i + 1 }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'rendering' do
|
||||
|
||||
before(:each) do
|
||||
@page = Factory.build(:page, :site => nil)
|
||||
@page.parts.build :slug => 'layout', :value => 'Hello world !'
|
||||
@page.parts.build :slug => 'sidebar', :value => 'A sidebar...'
|
||||
@page.send(:store_template)
|
||||
@layout = Factory.build(:layout, :site => nil)
|
||||
@layout.send(:store_template)
|
||||
@context = Liquid::Context.new
|
||||
end
|
||||
|
||||
context 'without layout' do
|
||||
|
||||
it 'should render the body part' do
|
||||
@page.render(@context).should == 'Hello world !'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'with layout' do
|
||||
|
||||
it 'should render both the body and sidebar parts' do
|
||||
@page.layout = @layout
|
||||
@page.render(@context).should == %{<html>
|
||||
<head>
|
||||
<title>My website</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="sidebar">A sidebar...</div>
|
||||
<div id="main">Hello world !</div>
|
||||
</body>
|
||||
</html>}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,3 +1,40 @@
|
||||
Locomotive.configure do |config|
|
||||
config.default_domain = 'example.com'
|
||||
end
|
||||
|
||||
module Locomotive
|
||||
class TestController
|
||||
|
||||
include Locomotive::Render
|
||||
|
||||
attr_accessor :output, :current_site
|
||||
|
||||
def render(options = {})
|
||||
self.output = options[:text]
|
||||
end
|
||||
|
||||
def response
|
||||
@response ||= TestResponse.new
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= TestRequest.new
|
||||
end
|
||||
end
|
||||
|
||||
class TestResponse
|
||||
|
||||
attr_accessor :headers
|
||||
|
||||
def initialize
|
||||
self.headers = {}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class TestRequest
|
||||
|
||||
attr_accessor :fullpath
|
||||
|
||||
end
|
||||
end
|
@ -23,7 +23,7 @@ module Mongoid #:nodoc:
|
||||
alias_method_chain :parentize, :custom_fields
|
||||
|
||||
def custom_fields_association_name(association_name)
|
||||
"#{association_name.singularize}_custom_fields".to_sym
|
||||
"#{association_name.to_s.singularize}_custom_fields".to_sym
|
||||
end
|
||||
|
||||
def custom_fields?(object, association_name)
|
||||
|
Loading…
Reference in New Issue
Block a user