implementation of editable_short_text in progress (tests failed)

This commit is contained in:
dinedine 2010-08-25 00:59:22 +02:00
parent e580021eda
commit be36c85e90
14 changed files with 328 additions and 52 deletions

View File

@ -3,7 +3,7 @@ source 'http://rubygems.org'
gem 'rails', '3.0.0.rc' gem 'rails', '3.0.0.rc'
gem 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => 'a41213c77cbc81dab87d' gem 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => 'dbcc0b1d9c189b3e3a13'
gem 'bson_ext', '>= 1.0.1' gem 'bson_ext', '>= 1.0.1'
gem 'mongoid', '2.0.0.beta.16' gem 'mongoid', '2.0.0.beta.16'

View File

@ -1,8 +1,8 @@
GIT GIT
remote: git://github.com/floehopper/mocha.git remote: git://github.com/floehopper/mocha.git
revision: d1715ff revision: e0a00a7
specs: specs:
mocha (0.9.8.20090918115329) mocha (0.9.8.20100819090654)
rake rake
GIT GIT
@ -13,14 +13,14 @@ GIT
GIT GIT
remote: git://github.com/locomotivecms/liquid.git remote: git://github.com/locomotivecms/liquid.git
revision: a41213c revision: dbcc0b1
ref: a41213c77cbc81dab87d ref: dbcc0b1d9c189b3e3a13
specs: specs:
liquid (2.1.3) liquid (2.1.3)
GIT GIT
remote: git://github.com/plataformatec/devise.git remote: git://github.com/plataformatec/devise.git
revision: 01c272c revision: 219c05c
specs: specs:
devise (1.2.0) devise (1.2.0)
bcrypt-ruby (~> 2.1.2) bcrypt-ruby (~> 2.1.2)
@ -96,13 +96,13 @@ GEM
term-ansicolor (~> 1.0.4) term-ansicolor (~> 1.0.4)
cucumber-rails (0.3.2) cucumber-rails (0.3.2)
cucumber (>= 0.8.0) cucumber (>= 0.8.0)
culerity (0.2.10) culerity (0.2.12)
daemons (1.1.0) daemons (1.1.0)
database_cleaner (0.5.2) database_cleaner (0.5.2)
diff-lcs (1.1.2) diff-lcs (1.1.2)
erubis (2.6.6) erubis (2.6.6)
abstract (>= 1.0.0) abstract (>= 1.0.0)
factory_girl (1.3.1) factory_girl (1.3.2)
factory_girl_rails (1.0) factory_girl_rails (1.0)
factory_girl (~> 1.3) factory_girl (~> 1.3)
rails (>= 3.0.0.beta4) rails (>= 3.0.0.beta4)
@ -135,7 +135,7 @@ GEM
gemcutter (>= 0.1.0) gemcutter (>= 0.1.0)
git (>= 1.2.5) git (>= 1.2.5)
rubyforge (>= 2.0.0) rubyforge (>= 2.0.0)
json_pure (1.4.3) json_pure (1.4.6)
launchy (0.3.7) launchy (0.3.7)
configuration (>= 0.0.5) configuration (>= 0.0.5)
rake (>= 0.8.1) rake (>= 0.8.1)
@ -170,7 +170,7 @@ GEM
pickle (>= 0.3.0) pickle (>= 0.3.0)
polyglot (0.3.1) polyglot (0.3.1)
rack (1.2.1) rack (1.2.1)
rack-mount (0.6.9) rack-mount (0.6.12)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (0.5.4) rack-test (0.5.4)
rack (>= 1.0) rack (>= 1.0)
@ -211,7 +211,7 @@ GEM
rubyforge (2.0.4) rubyforge (2.0.4)
json_pure (>= 1.1.7) json_pure (>= 1.1.7)
rubyzip (0.9.4) rubyzip (0.9.4)
selenium-webdriver (0.0.27) selenium-webdriver (0.0.28)
ffi (>= 0.6.1) ffi (>= 0.6.1)
json_pure json_pure
rubyzip rubyzip

View File

@ -0,0 +1,25 @@
class EditableElement
include Mongoid::Document
## fields ##
field :slug
field :block
field :content
field :default_content
field :hint
field :disabled, :type => Boolean, :default => false
## associations ##
embedded_in :page, :inverse_of => :editable_elements
## validations ##
validates_presence_of :slug
## methods ##
def content
self.read_attribute(:content).blank? ? self.default_content : self.read_attribute(:content)
end
end

View File

@ -0,0 +1,51 @@
module Models
module Extensions
module Page
module EditableElements
extend ActiveSupport::Concern
included do
embeds_many :editable_elements
end
module InstanceMethods
def find_editable_element(block, slug)
self.editable_elements.detect { |el| el.block == block && el.slug == slug }
end
def add_or_update_editable_element(attributes)
element = self.find_editable_element(attributes[:block], attributes[:slug])
if element
element.attributes = attributes
else
self.editable_elements.build(attributes)
end
end
def disable_all_editable_elements
self.editable_elements.each { |el| el.disabled = true }
end
def enable_editable_elements(block)
self.editable_elements.each { |el| el.disabled = false if el.block == block }
end
def merge_editable_elements_from_page(source)
source.editable_elements.each do |el|
existing_el = self.find_editable_element(el.block, el.slug)
if existing_el.nil? # new one from parents
self.editable_elements.build(el.attributes.merge(:disabled => true))
end
end
end
end
end
end
end
end

View File

@ -31,6 +31,7 @@ module Models
@template_changed = true @template_changed = true
@parsing_errors = [] @parsing_errors = []
begin begin
self._parse_and_serialize_template self._parse_and_serialize_template
rescue ::Liquid::SyntaxError => error rescue ::Liquid::SyntaxError => error
@ -51,35 +52,42 @@ module Models
end end
def parse(context = {}) def parse(context = {})
@template = ::Liquid::Template.parse(self.raw_template, { :site => self.site, :page => self }.merge(context)) self.disable_all_editable_elements
default_context = { :site => self.site, :page => self, :templates => [], :snippets => [] }
@template = ::Liquid::Template.parse(self.raw_template, default_context.merge(context))
self.template_dependencies = context[:templates]
self.snippet_dependencies = context[:snippets]
@template.root.context.clear @template.root.context.clear
#
dependencies = all_dependencies(@template.root, { :templates => [], :snippets => [] }) # dependencies = all_dependencies(@template.root, { :templates => [], :snippets => [] })
#
self.template_dependencies = dependencies[:templates] # self.template_dependencies = dependencies[:templates]
self.snippet_dependencies = dependencies[:snippets] # self.snippet_dependencies = dependencies[:snippets]
end end
def template_must_be_valid def template_must_be_valid
@parsing_errors.try(:each) { |msg| self.errors.add :template, msg } @parsing_errors.try(:each) { |msg| self.errors.add :template, msg }
end end
def all_dependencies(node, dependencies = {}) # def all_dependencies(node, dependencies = {})
case node # case node
when Locomotive::Liquid::Tags::Extends # when Locomotive::Liquid::Tags::Extends
dependencies[:templates] << node.page_id # dependencies[:templates] << node.page_id
when Locomotive::Liquid::Tags::Snippet # when Locomotive::Liquid::Tags::Snippet
dependencies[:snippets] << node.slug # dependencies[:snippets] << node.slug
end # end
#
if node.respond_to?(:nodelist) && node.nodelist # if node.respond_to?(:nodelist) && node.nodelist
node.nodelist.each do |child| # node.nodelist.each do |child|
self.all_dependencies(child, dependencies) # self.all_dependencies(child, dependencies)
end # end
end # end
#
dependencies # dependencies
end # end
def update_template_descendants def update_template_descendants
return unless @template_changed == true return unless @template_changed == true

View File

@ -4,7 +4,7 @@ class Page
## Extensions ## ## Extensions ##
include Models::Extensions::Page::Tree include Models::Extensions::Page::Tree
# include Models::Extensions::Page::Parts include Models::Extensions::Page::EditableElements
include Models::Extensions::Page::Parse include Models::Extensions::Page::Parse
include Models::Extensions::Page::Render include Models::Extensions::Page::Render
include Models::Extensions::Page::Templatized include Models::Extensions::Page::Templatized

View File

@ -41,8 +41,11 @@ class Snippet
end end
def _change_snippet_inside_template(node) def _change_snippet_inside_template(node)
if node.is_a?(Locomotive::Liquid::Tags::Snippet) case node
node.refresh(self) when Locomotive::Liquid::Tags::Snippet
node.refresh(self) if node.slug == self.slug
when Locomotive::Liquid::Tags::Block
self._change_snippet_inside_template(node.parent) if node.parent
else else
if node.respond_to?(:nodelist) if node.respond_to?(:nodelist)
node.nodelist.each do |child| node.nodelist.each do |child|

View File

@ -0,0 +1,55 @@
Feature: Editable elements
As a designer
I want to define content which will be edited by the website editor
Background:
Given I have the site: "test site" set up
Scenario: Simple short text element
Given a page named "hello-world" with the template:
"""
My application says {% editable_short_text 'a_sentence', hint: 'please enter a new sentence' %}Hello world{% endeditable_short_text %}
"""
When I view the rendered page at "/hello-world"
Then the rendered output should look like:
"""
My application says Hello world
"""
Scenario: Modified short text element
Given a page named "hello-world" with the template:
"""
My application says {% editable_short_text 'a_sentence', hint: 'please enter a new sentence' %}Hello world{% endeditable_short_text %}
"""
And the editable element "a_sentence" with the content "Bonjour" in the "hello-world" page
When I view the rendered page at "/hello-world"
Then the rendered output should look like:
"""
My application says Bonjour
"""
Scenario: Short text element inside a block
Given a page named "hello-world" with the template:
"""
{% block main %}My application says {% editable_short_text 'a_sentence' %}Hello world{% endeditable_short_text %}{% endblock %}
"""
When I view the rendered page at "/hello-world"
Then the rendered output should look like:
"""
My application says Hello world
"""
Scenario: Not modified short text element inside a block and with page inheritance
Given a page named "hello-world" with the template:
"""
{% block main %}My application says {% editable_short_text 'a_sentence' %}Hello world{% endeditable_short_text %}{% endblock %}
"""
Given a page named "another-hello-world" with the template:
"""
{% extends hello-world %}
"""
When I view the rendered page at "/another-hello-world"
Then the rendered output should look like:
"""
My application says Hello world
"""

View File

@ -0,0 +1,10 @@
Given /^a simple page named "([^"]*)" with the body:$/ do |page_slug, page_contents|
@page = create_content_page(page_slug, page_contents)
end
# modify an editable element
Given /^the editable element "([^"]*)" with the content "([^"]*)" in the "([^"]*)" page$/ do |slug, content, page_slug|
page = @site.pages.where(:slug => page_slug).first
page.find_editable_element(nil, slug).content = content
page.save!
end

View File

@ -0,0 +1,48 @@
module Locomotive
module Liquid
module Tags
class EditableShortText < ::Liquid::Block
Syntax = /(#{::Liquid::QuotedFragment})(\s*,\s*#{::Liquid::Expression}+)?/
def initialize(tag_name, markup, tokens, context)
if markup =~ Syntax
@slug = $1.gsub('\'', '')
@options = {}
markup.scan(::Liquid::TagAttributes) { |key, value| @options[key.to_sym] = value.gsub(/^'/, '').gsub(/'$/, '') }
else
raise ::Liquid::SyntaxError.new("Syntax Error in 'editable_short_text' - Valid syntax: editable_short_text <slug>(, <options>)")
end
super
puts "@nodelist = #{@nodelist.inspect}"
@context[:page].add_or_update_editable_element({
:block => @context[:current_block].try(:name),
:slug => @slug,
:hint => @options[:hint],
:default_content => @nodelist.first.to_s,
:disabled => false
})
end
def render(context)
current_page = context.registers[:page]
puts "[EditableShortText] rendering #{context['block'].inspect} / #{current_page.editable_elements.inspect}"
element = current_page.find_editable_element(context['block'].try(:name), @slug)
if element
element.content
else
Locomotive.logger "[editable short text] missing editable short text #{context[:block].name} / #{@slug}"
''
end
end
::Liquid::Template.register_tag('editable_short_text', EditableShortText)
end
end
end
end

View File

@ -3,26 +3,27 @@ module Locomotive
module Tags module Tags
class Extends < ::Liquid::Extends class Extends < ::Liquid::Extends
attr_accessor :page_id attr_accessor :page_id # parent page id
def parse_parent_template(context) def initialize(tag_name, markup, tokens, context)
page = nil if markup =~ Syntax
@template_name = $1
if @template_name == 'parent'
if context[:cached_parent]
page = context[:cached_parent]
context[:cached_parent] = nil
else
page = context[:page].parent
end
else else
path = @template_name.gsub("'", '') raise SyntaxError.new("Error in tag 'extends' - Valid syntax: extends [template]")
page = context[:cached_pages].try(:[], path) || context[:site].pages.where(:fullpath => path).first
end end
raise PageNotFound.new("Page with fullpath '#{@template_name}' was not found") if page.nil? retrieve_parent_page
@page_id = page.id # before parsing the embedded tokens, get editable elements from parent page
@context[:page].merge_editable_elements_from_page(@context[:parent_page])
super
end
private
def parse_parent_template
page = @context[:parent_page]
template = page.template template = page.template
@ -30,13 +31,35 @@ module Locomotive
blocks = find_blocks(template.root.nodelist) blocks = find_blocks(template.root.nodelist)
blocks.each_value do |block| blocks.each_value do |block|
block.send(:instance_variable_set, :"@context", context) block.send(:instance_variable_set, :"@context", @context)
block.end_tag block.end_tag
end end
@context[:snippets] = page.snippet_dependencies
@context[:templates] = [*page.template_dependencies] + [@page_id]
template template
end end
def retrieve_parent_page
if @template_name == 'parent'
if @context[:cached_parent]
@context[:parent] = @context[:cached_parent]
@context[:cached_parent] = nil
else
@context[:parent_page] = @context[:page].parent
end
else
path = @template_name.gsub("'", '')
@context[:parent_page] = @context[:cached_pages].try(:[], path) ||
@context[:site].pages.where(:fullpath => path).first
end
raise PageNotFound.new("Page with fullpath '#{@template_name}' was not found") if @context[:parent_page].nil?
@page_id = @context[:parent_page].id
end
end end
::Liquid::Template.register_tag('extends', Extends) ::Liquid::Template.register_tag('extends', Extends)

View File

@ -0,0 +1,31 @@
module Locomotive
module Liquid
module Tags
class InheritedBlock < ::Liquid::InheritedBlock
def end_tag
super
if self.contains_super?(@nodelist) # then enable all editable_elements (coming from the parent block too)
@context[:page].enable_editable_elements(@name)
end
end
protected
def contains_super?(nodelist)
nodelist.any? do |node|
if node.is_a?(String) && node =~ /\{\{\s*block.super\s*\}\}/
true
elsif node.respond_to?(:nodelist) && !node.is_a?(Locomotive::Liquid::Tags::InheritedBlock)
contains_super?(node.nodelist)
end
end
end
end
::Liquid::Template.register_tag('block', InheritedBlock)
end
end
end

View File

@ -12,6 +12,8 @@ module Locomotive
@slug = @template_name.gsub('\'', '') @slug = @template_name.gsub('\'', '')
context[:snippets] << @slug
snippet = context[:site].snippets.where(:slug => @slug).first snippet = context[:site].snippets.where(:slug => @slug).first
self.refresh(snippet, context) if snippet self.refresh(snippet, context) if snippet

View File

@ -0,0 +1,20 @@
require 'spec_helper'
describe Locomotive::Liquid::Tags::EditableShortText do
it 'should have a valid syntax' do
markup = "'title', hint: 'Simple short text'"
lambda do
Locomotive::Liquid::Tags::EditableShortText.new('editable_short_text', markup, ["{% endeditable_short_text %}"], {})
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
end