From be36c85e90776692f6342b4bcb7d944c1fb3c5ac Mon Sep 17 00:00:00 2001 From: dinedine Date: Wed, 25 Aug 2010 00:59:22 +0200 Subject: [PATCH] implementation of editable_short_text in progress (tests failed) --- Gemfile | 2 +- Gemfile.lock | 20 +++---- app/models/editable_element.rb | 25 +++++++++ .../extensions/page/editable_elements.rb | 51 +++++++++++++++++ app/models/extensions/page/parse.rb | 52 ++++++++++-------- app/models/page.rb | 2 +- app/models/snippet.rb | 7 ++- features/engine/editable_elements.feature | 55 +++++++++++++++++++ .../editable_elements_steps.rb | 10 ++++ .../liquid/tags/editable_short_text.rb | 48 ++++++++++++++++ lib/locomotive/liquid/tags/extends.rb | 55 +++++++++++++------ lib/locomotive/liquid/tags/inherited_block.rb | 31 +++++++++++ lib/locomotive/liquid/tags/snippet.rb | 2 + .../liquid/tags/editable_short_text_spec.rb | 20 +++++++ 14 files changed, 328 insertions(+), 52 deletions(-) create mode 100644 app/models/editable_element.rb create mode 100644 app/models/extensions/page/editable_elements.rb create mode 100644 features/engine/editable_elements.feature create mode 100644 features/step_definitions/editable_elements_steps.rb create mode 100644 lib/locomotive/liquid/tags/editable_short_text.rb create mode 100644 lib/locomotive/liquid/tags/inherited_block.rb create mode 100644 spec/lib/locomotive/liquid/tags/editable_short_text_spec.rb diff --git a/Gemfile b/Gemfile index c3945388..9a90b211 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'http://rubygems.org' 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 'mongoid', '2.0.0.beta.16' diff --git a/Gemfile.lock b/Gemfile.lock index 8bbc3387..1650aa64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: git://github.com/floehopper/mocha.git - revision: d1715ff + revision: e0a00a7 specs: - mocha (0.9.8.20090918115329) + mocha (0.9.8.20100819090654) rake GIT @@ -13,14 +13,14 @@ GIT GIT remote: git://github.com/locomotivecms/liquid.git - revision: a41213c - ref: a41213c77cbc81dab87d + revision: dbcc0b1 + ref: dbcc0b1d9c189b3e3a13 specs: liquid (2.1.3) GIT remote: git://github.com/plataformatec/devise.git - revision: 01c272c + revision: 219c05c specs: devise (1.2.0) bcrypt-ruby (~> 2.1.2) @@ -96,13 +96,13 @@ GEM term-ansicolor (~> 1.0.4) cucumber-rails (0.3.2) cucumber (>= 0.8.0) - culerity (0.2.10) + culerity (0.2.12) daemons (1.1.0) database_cleaner (0.5.2) diff-lcs (1.1.2) erubis (2.6.6) abstract (>= 1.0.0) - factory_girl (1.3.1) + factory_girl (1.3.2) factory_girl_rails (1.0) factory_girl (~> 1.3) rails (>= 3.0.0.beta4) @@ -135,7 +135,7 @@ GEM gemcutter (>= 0.1.0) git (>= 1.2.5) rubyforge (>= 2.0.0) - json_pure (1.4.3) + json_pure (1.4.6) launchy (0.3.7) configuration (>= 0.0.5) rake (>= 0.8.1) @@ -170,7 +170,7 @@ GEM pickle (>= 0.3.0) polyglot (0.3.1) rack (1.2.1) - rack-mount (0.6.9) + rack-mount (0.6.12) rack (>= 1.0.0) rack-test (0.5.4) rack (>= 1.0) @@ -211,7 +211,7 @@ GEM rubyforge (2.0.4) json_pure (>= 1.1.7) rubyzip (0.9.4) - selenium-webdriver (0.0.27) + selenium-webdriver (0.0.28) ffi (>= 0.6.1) json_pure rubyzip diff --git a/app/models/editable_element.rb b/app/models/editable_element.rb new file mode 100644 index 00000000..bbe622fb --- /dev/null +++ b/app/models/editable_element.rb @@ -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 \ No newline at end of file diff --git a/app/models/extensions/page/editable_elements.rb b/app/models/extensions/page/editable_elements.rb new file mode 100644 index 00000000..5bbd2d1c --- /dev/null +++ b/app/models/extensions/page/editable_elements.rb @@ -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 \ No newline at end of file diff --git a/app/models/extensions/page/parse.rb b/app/models/extensions/page/parse.rb index a0293a72..fce8a80b 100644 --- a/app/models/extensions/page/parse.rb +++ b/app/models/extensions/page/parse.rb @@ -31,6 +31,7 @@ module Models @template_changed = true @parsing_errors = [] + begin self._parse_and_serialize_template rescue ::Liquid::SyntaxError => error @@ -51,35 +52,42 @@ module Models end 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 - - dependencies = all_dependencies(@template.root, { :templates => [], :snippets => [] }) - - self.template_dependencies = dependencies[:templates] - self.snippet_dependencies = dependencies[:snippets] + # + # dependencies = all_dependencies(@template.root, { :templates => [], :snippets => [] }) + # + # self.template_dependencies = dependencies[:templates] + # self.snippet_dependencies = dependencies[:snippets] end def template_must_be_valid @parsing_errors.try(:each) { |msg| self.errors.add :template, msg } end - def all_dependencies(node, dependencies = {}) - case node - when Locomotive::Liquid::Tags::Extends - dependencies[:templates] << node.page_id - when Locomotive::Liquid::Tags::Snippet - dependencies[:snippets] << node.slug - end - - if node.respond_to?(:nodelist) && node.nodelist - node.nodelist.each do |child| - self.all_dependencies(child, dependencies) - end - end - - dependencies - end + # def all_dependencies(node, dependencies = {}) + # case node + # when Locomotive::Liquid::Tags::Extends + # dependencies[:templates] << node.page_id + # when Locomotive::Liquid::Tags::Snippet + # dependencies[:snippets] << node.slug + # end + # + # if node.respond_to?(:nodelist) && node.nodelist + # node.nodelist.each do |child| + # self.all_dependencies(child, dependencies) + # end + # end + # + # dependencies + # end def update_template_descendants return unless @template_changed == true diff --git a/app/models/page.rb b/app/models/page.rb index add97c45..72213238 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -4,7 +4,7 @@ class Page ## Extensions ## include Models::Extensions::Page::Tree - # include Models::Extensions::Page::Parts + include Models::Extensions::Page::EditableElements include Models::Extensions::Page::Parse include Models::Extensions::Page::Render include Models::Extensions::Page::Templatized diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 063cc4c5..7d9e6fed 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -41,8 +41,11 @@ class Snippet end def _change_snippet_inside_template(node) - if node.is_a?(Locomotive::Liquid::Tags::Snippet) - node.refresh(self) + case node + 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 if node.respond_to?(:nodelist) node.nodelist.each do |child| diff --git a/features/engine/editable_elements.feature b/features/engine/editable_elements.feature new file mode 100644 index 00000000..a1e31f24 --- /dev/null +++ b/features/engine/editable_elements.feature @@ -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 + """ \ No newline at end of file diff --git a/features/step_definitions/editable_elements_steps.rb b/features/step_definitions/editable_elements_steps.rb new file mode 100644 index 00000000..b87386d7 --- /dev/null +++ b/features/step_definitions/editable_elements_steps.rb @@ -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 \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/editable_short_text.rb b/lib/locomotive/liquid/tags/editable_short_text.rb new file mode 100644 index 00000000..8271d3ae --- /dev/null +++ b/lib/locomotive/liquid/tags/editable_short_text.rb @@ -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 (, )") + 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 diff --git a/lib/locomotive/liquid/tags/extends.rb b/lib/locomotive/liquid/tags/extends.rb index 8543f03a..2f4e9ac3 100644 --- a/lib/locomotive/liquid/tags/extends.rb +++ b/lib/locomotive/liquid/tags/extends.rb @@ -3,26 +3,27 @@ module Locomotive module Tags class Extends < ::Liquid::Extends - attr_accessor :page_id + attr_accessor :page_id # parent page id - def parse_parent_template(context) - page = nil - - if @template_name == 'parent' - if context[:cached_parent] - page = context[:cached_parent] - context[:cached_parent] = nil - else - page = context[:page].parent - end + def initialize(tag_name, markup, tokens, context) + if markup =~ Syntax + @template_name = $1 else - path = @template_name.gsub("'", '') - page = context[:cached_pages].try(:[], path) || context[:site].pages.where(:fullpath => path).first + raise SyntaxError.new("Error in tag 'extends' - Valid syntax: extends [template]") 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 @@ -30,13 +31,35 @@ module Locomotive blocks = find_blocks(template.root.nodelist) blocks.each_value do |block| - block.send(:instance_variable_set, :"@context", context) + block.send(:instance_variable_set, :"@context", @context) block.end_tag end + @context[:snippets] = page.snippet_dependencies + @context[:templates] = [*page.template_dependencies] + [@page_id] + template 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 ::Liquid::Template.register_tag('extends', Extends) diff --git a/lib/locomotive/liquid/tags/inherited_block.rb b/lib/locomotive/liquid/tags/inherited_block.rb new file mode 100644 index 00000000..79f7656b --- /dev/null +++ b/lib/locomotive/liquid/tags/inherited_block.rb @@ -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 \ No newline at end of file diff --git a/lib/locomotive/liquid/tags/snippet.rb b/lib/locomotive/liquid/tags/snippet.rb index f4bf5ec6..00a207f8 100644 --- a/lib/locomotive/liquid/tags/snippet.rb +++ b/lib/locomotive/liquid/tags/snippet.rb @@ -12,6 +12,8 @@ module Locomotive @slug = @template_name.gsub('\'', '') + context[:snippets] << @slug + snippet = context[:site].snippets.where(:slug => @slug).first self.refresh(snippet, context) if snippet diff --git a/spec/lib/locomotive/liquid/tags/editable_short_text_spec.rb b/spec/lib/locomotive/liquid/tags/editable_short_text_spec.rb new file mode 100644 index 00000000..84742ea7 --- /dev/null +++ b/spec/lib/locomotive/liquid/tags/editable_short_text_spec.rb @@ -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