Add section tag to steam

Julien Girard committed Apr 27, 2018
commit f51be3ca204a1362ec0d0dc70f9073a9619bda1f
Showing 27 changed files with 516 additions and 26 deletions
locomotive/steam/adapters/filesystem.rb b/lib/locomotive/steam/adapters/filesystem.rb +2 -7
@@ @@ -9,7 +9,6 @@ require_relative 'filesystem/sanitizer'
require_relative_all 'filesystem/sanitizers'
module Locomotive::Steam
-
class FilesystemAdapter
include Morphine
@@ @@ -116,7 +115,7 @@ module Locomotive::Steam
end
def build_yaml_loaders
- %i(sites pages content_types content_entries snippets translations theme_assets).inject({}) do |memo, name|
+ %i(sites pages content_types content_entries snippets sections translations theme_assets).inject({}) do |memo, name|
memo[name] = build_klass('YAMLLoaders', name).new(site_path)
memo
end
@@ @@ -124,7 +123,7 @@ module Locomotive::Steam
def build_sanitizers
hash = Hash.new { build_klass('Sanitizers', :simple).new }
- %i(sites pages content_types content_entries snippets).inject(hash) do |memo, name|
+ %i(sites pages content_types content_entries snippets sections).inject(hash) do |memo, name|
memo[name] = build_klass('Sanitizers', name).new
memo
end
@@ @@ -138,9 +137,5 @@ module Locomotive::Steam
def site_path
options.respond_to?(:has_key?) ? options[:path] : options
end
-
end
-
end
-
-
locomotive/steam/adapters/filesystem/sanitizer.rb b/lib/locomotive/steam/adapters/filesystem/sanitizer.rb +0 -2
@@ @@ -1,9 +1,7 @@
module Locomotive::Steam
module Adapters
module Filesystem
-
module Sanitizer
-
extend Forwardable
def_delegators :@scope, :site, :locale, :locales, :default_locale
locomotive/steam/adapters/filesystem/sanitizers/section.rb b/lib/locomotive/steam/adapters/filesystem/sanitizers/section.rb +27 -0
@@ @@ -0,0 +1,27 @@
+ module Locomotive::Steam
+ module Adapters
+ module Filesystem
+ module Sanitizers
+ class Section
+
+ include Adapters::Filesystem::Sanitizer
+
+ def apply_to_entity(entity)
+ super
+ parse_json(entity)
+ end
+
+ private
+
+ def parse_json(entity)
+ json_formatter = /^---(?<json>(\s*\n.*?\n?))^---/mo
+ file_path = entity.template_path
+ file_content = File.read(file_path)
+ json = file_content.match(json_formatter)
+ entity.definition = JSON.parse(json[:json])
+ end
+ end
+ end
+ end
+ end
+ end
locomotive/steam/adapters/filesystem/yaml_loaders/section.rb b/lib/locomotive/steam/adapters/filesystem/yaml_loaders/section.rb +63 -0
@@ @@ -0,0 +1,63 @@
+ module Locomotive
+ module Steam
+ module Adapters
+ module Filesystem
+ module YAMLLoaders
+ class Section
+
+ include Adapters::Filesystem::YAMLLoader
+
+ def load(scope)
+ super
+ load_list
+ end
+
+ private
+
+ def load_list
+ {}.tap do |hash|
+ each_file do |filepath, slug, locale|
+ _locale = locale || default_locale
+
+ if element = hash[slug]
+ update(element, filepath, _locale)
+ else
+ element = build(filepath, slug, _locale)
+ end
+
+ hash[slug] = element
+ end
+ end.values
+ end
+
+ def build(filepath, slug, locale)
+ {
+ name: slug.humanize,
+ slug: slug,
+ template_path: { locale => filepath }
+ }
+ end
+
+ def update(element, filepath, locale)
+ element[:template_path][locale] = filepath
+ end
+
+ def each_file(&block)
+ Dir.glob(File.join(path, "*.{#{template_extensions.join(',')}}")).each do |filepath|
+
+ slug, locale = File.basename(filepath).split('.')[0..1]
+ locale = default_locale if template_extensions.include?(locale)
+
+ yield(filepath, slug.permalink, locale.to_sym)
+ end
+ end
+
+ def path
+ @path ||= File.join(site_path, 'app', 'views', 'sections')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
locomotive/steam/entities/section.rb b/lib/locomotive/steam/entities/section.rb +18 -0
@@ @@ -0,0 +1,18 @@
+ module Locomotive::Steam
+ class Section
+
+ include Locomotive::Steam::Models::Entity
+
+ def initialize(attributes = {})
+ super({
+ template: nil,
+ source: nil,
+ definition: nil
+ }.merge(attributes))
+ end
+
+ def source
+ self[:template]
+ end
+ end
+ end
locomotive/steam/liquid/errors.rb b/lib/locomotive/steam/liquid/errors.rb +2 -0
@@ @@ -5,6 +5,8 @@ module Locomotive
class SnippetNotFound < ::Liquid::Error; end
+ class SectionNotFound < ::Liquid::Error; end
+
class PageNotTranslated < ::Liquid::Error; end
end
end
locomotive/steam/liquid/tags/section.rb b/lib/locomotive/steam/liquid/tags/section.rb +47 -0
@@ @@ -0,0 +1,47 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+ class Section < ::Liquid::Include
+
+ def render(context)
+ @template_name = evaluate_snippet_name(context)
+ # @options doesn't include the page key if cache is on
+ @options[:page] = context.registers[:page]
+ if find_section(context).definition[:default]
+ context['section'] = find_section(context).definition[:default] # | mongoDB content if exists
+ end
+ begin
+ super
+ rescue Locomotive::Steam::ParsingRenderingError => e
+ e.file = @template_name + ' [Section]'
+ raise e
+ end
+ end
+
+ private
+
+ def read_template_from_file_system(context)
+ section = find_section(context)
+ raise SectionNotFound.new("Section with slug '#{@template_name}' was not found") if section.nil?
+ section.liquid_source
+ end
+
+ def find_section(context)
+ context.registers[:services].section_finder.find(@template_name)
+ end
+
+ # Repeat snippet
+ def evaluate_snippet_name(context = nil)
+ context.try(:evaluate, @template_name) ||
+ (!@template_name.is_a?(String) && @template_name.send(:state).first) ||
+ @template_name
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('section'.freeze, Section)
+ end
+ end
+ end
+ end
locomotive/steam/liquid/template.rb b/lib/locomotive/steam/liquid/template.rb +0 -5
@@ @@ -1,7 +1,6 @@
module Locomotive
module Steam
module Liquid
-
class Template < ::Liquid::Template
# When we render a Locomotive template, we need to know what are
@@ @@ -18,18 +17,14 @@ module Locomotive
end
class << self
-
def parse(source, options = {})
template = new
template.parse(source, options.merge({
line_numbers: true
}))
end
-
end
-
end
-
end
end
end
locomotive/steam/models/entity.rb b/lib/locomotive/steam/models/entity.rb +0 -2
@@ @@ -1,6 +1,5 @@
module Locomotive::Steam
module Models
-
module Entity
include Locomotive::Steam::Models::Concerns::Validation
@@ @@ -47,7 +46,6 @@ module Locomotive::Steam
def serialize
attributes.dup
end
-
end
end
end
locomotive/steam/repositories.rb b/lib/locomotive/steam/repositories.rb +4 -0
@@ @@ -25,6 +25,10 @@ module Locomotive
SnippetRepository.new(adapter, current_site, locale)
end
+ register :section do
+ SectionRepository.new(adapter, current_site, locale)
+ end
+
register :content_type do
ContentTypeRepository.new(adapter, current_site, locale)
end
locomotive/steam/repositories/section_repository.rb b/lib/locomotive/steam/repositories/section_repository.rb +14 -0
@@ @@ -0,0 +1,14 @@
+ module Locomotive
+ module Steam
+ class SectionRepository
+
+ include Models::Repository
+
+ mapping :sections, entity: Section
+
+ def by_slug(slug)
+ first { where(slug: slug) }
+ end
+ end
+ end
+ end
locomotive/steam/repositories/snippet_repository.rb b/lib/locomotive/steam/repositories/snippet_repository.rb +0 -3
@@ @@ -1,11 +1,9 @@
module Locomotive
module Steam
-
class SnippetRepository
include Models::Repository
- # Entity mapping
mapping :snippets, entity: Snippet do
localized_attributes :template_path, :template, :source
end
@@ @@ -13,7 +11,6 @@ module Locomotive
def by_slug(slug)
first { where(slug: slug) }
end
-
end
end
end
locomotive/steam/services.rb b/lib/locomotive/steam/services.rb +4 -0
@@ @@ -62,6 +62,10 @@ module Locomotive
Steam::SnippetFinderService.new(repositories.snippet)
end
+ register :section_finder do
+ Steam::SectionFinderService.new(repositories.section)
+ end
+
register :action do
Steam::ActionService.new(current_site, email, content_entry: content_entry, api: external_api, redirection: page_redirection)
end
locomotive/steam/services/concerns/decorator.rb b/lib/locomotive/steam/services/concerns/decorator.rb +0 -2
@@ @@ -26,9 +26,7 @@ module Locomotive
def default_locale
repository.site.default_locale
end
-
end
-
end
end
end
locomotive/steam/services/section_finder_service.rb b/lib/locomotive/steam/services/section_finder_service.rb +17 -0
@@ @@ -0,0 +1,17 @@
+ module Locomotive
+ module Steam
+ class SectionFinderService
+
+ include Locomotive::Steam::Services::Concerns::Decorator
+
+ attr_accessor_initialize :repository
+
+ def find(slug)
+ decorate do
+ repository.by_slug(slug)
+ end
+ end
+ end
+ end
+ end
+
spec/fixtures/default/app/views/pages/tags/section.liquid.haml +11 -0
@@ @@ -0,0 +1,11 @@
+ ---
+ title: sectionTemplateTest
+ template: true
+ listed: true
+ position: 5
+ ---
+ {% section "header" %}
+
+ {% section_dropzone %}
+
+ {% section "footer" %}
spec/fixtures/default/app/views/sections/carousel.liquid +21 -0
@@ @@ -0,0 +1,21 @@
+ ---
+ {
+ "name": "carousel",
+ "static": false,
+ "category": "carousel",
+ "class": "section-carousel",
+ "settings":
+ [
+ {
+ "id": "brand",
+ "type": "text",
+ "label": "Text to display in the carousel",
+ "default": "Header"
+ }
+ ]
+ }
+ ---
+
+ <h2>
+ FOOTER PLAIN TEXT
+ </h2>
spec/fixtures/default/app/views/sections/footer.liquid +49 -0
@@ @@ -0,0 +1,49 @@
+ ---
+ {
+ "name": "footer",
+ "static": true,
+ "category": "footer",
+ "class": "section-footer",
+ "settings":
+ [
+ {
+ "id": "brand",
+ "type": "text",
+ "label": "Text to display in the footer",
+ "default": "Header"
+ }
+ ],
+ "default":
+ {
+ "settings":
+ {
+ "brand": "MY COMPANY"
+ },
+ "blocks":
+ [
+ {
+ "type": "link",
+ "settings":
+ {
+ "label": "Link #1",
+ "url": "https://www.nocoffee.fr",
+ "new_tab": "true"
+ }
+ },
+ {
+ "type": "link",
+ "settings":
+ {
+ "label": "Link #2",
+ "url": "https://www.nocoffee.fr",
+ "new_tab": "true"
+ }
+ }
+ ]
+ }
+ }
+ ---
+
+ <h2>
+ FOOTER PLAIN TEXT
+ </h2>
spec/fixtures/default/app/views/sections/header.liquid +55 -0
@@ @@ -0,0 +1,55 @@
+ ---
+ {
+ "name": "header",
+ "static": true,
+ "category": "header",
+ "class": "section-header",
+ "settings":
+ [
+ {
+ "id": "brand",
+ "type": "text",
+ "label": "Text to display in the header",
+ "default": "Header"
+ }
+ ],
+ "default":
+ {
+ "settings":
+ {
+ "brand": "MY COMPANY"
+ },
+ "blocks":
+ [
+ {
+ "type": "link",
+ "settings":
+ {
+ "label": "Link #1",
+ "url": "https://www.nocoffee.fr",
+ "new_tab": "true"
+ }
+ },
+ {
+ "type": "link",
+ "settings":
+ {
+ "label": "Link #2",
+ "url": "https://www.nocoffee.fr",
+ "new_tab": "true"
+ }
+ }
+ ]
+ }
+ }
+ ---
+ <h1> {{ section.settings.brand }} </h1>
+ <ul>
+ {% for block in section.blocks %}
+ <li>
+ <a href="{{ block.settings.url }}" target="{% if block.settings.new_tab %}_blank{% endif %}">
+ {{ block.settings.label }}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
spec/unit/adapters/filesystem/sanitizers/section_spec.rb +26 -0
@@ @@ -0,0 +1,26 @@
+ require 'spec_helper'
+
+ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/sanitizer.rb'
+ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/sanitizers/section.rb'
+
+ describe Locomotive::Steam::Adapters::Filesystem::Sanitizers::Section do
+
+ let(:template_path) { 'spec/fixtures/default/app/views/sections/header.liquid' }
+ let(:entity) { instance_double('SectionEntity', template_path: template_path) }
+ let(:site) { instance_double('Site', _id: 1) }
+ let(:scope) { instance_double('Scope', site: site) }
+ let(:sanitizer) { described_class.new }
+
+ before(:each) do
+ sanitizer.setup(scope);
+ end
+
+ describe '#apply_to_entity' do
+ subject { sanitizer.apply_to_entity(entity) }
+ it 'sanitize entity' do
+ expect(entity).to receive(:definition=).with(hash_including({"name" => 'header'}))
+ expect(entity).to receive(:[]=).with(:site_id, 1)
+ subject
+ end
+ end
+ end
spec/unit/adapters/filesystem/yaml_loaders/section_spec.rb +27 -0
@@ @@ -0,0 +1,27 @@
+ require 'spec_helper'
+
+ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_loader.rb'
+ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_loaders/section.rb'
+
+ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::Section do
+
+ let(:site_path) { default_fixture_site_path }
+ let(:loader) { described_class.new(site_path) }
+
+ describe '#load' do
+
+ let(:scope) { instance_double('Scope') }
+
+ subject { loader.load(scope).sort { |a, b| a[:name] <=> b[:name] } }
+
+ it 'tests various stuff' do
+ expect(subject.size).to eq 3
+ expect(subject.first[:slug]).to eq('carousel')
+ expect(subject[1][:name]).to eq('Footer')
+ expect(subject[2][:name]).to eq('Header')
+ expect(subject[2][:slug]).to eq('header')
+ end
+
+ end
+
+ end
spec/unit/entities/section_spec.rb +23 -0
@@ @@ -0,0 +1,23 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::Section do
+
+ let(:attributes) { {} }
+ let(:section) { described_class.new(attributes) }
+
+ describe '#source' do
+
+ let(:attributes) { { template: "Hello world"} }
+
+ subject { section.source }
+ it { is_expected.to eq 'Hello world' }
+
+ end
+
+ describe '#definition' do
+ let(:attributes) { { definition: { name: 'aName' } } }
+ subject { section.definition }
+ it { is_expected.to eq({ 'name' => 'aName' }) }
+ end
+
+ end
spec/unit/liquid/tags/section_spec.rb +40 -0
@@ @@ -0,0 +1,40 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::Liquid::Tags::Section do
+
+ let(:services) { Locomotive::Steam::Services.build_instance(nil) }
+ let(:finder) { services.section_finder }
+ let(:source) { "Locomotive {% section header %}" }
+ let(:context) { ::Liquid::Context.new({}, {}, { services: services }) }
+
+ before { allow(finder).to receive(:find).and_return(section) }
+
+ describe 'rendering' do
+
+ let(:section) { instance_double(
+ 'Section',
+ liquid_source: 'built by NoCoffee',
+ definition: {
+ default: 'some default JSON'
+ }
+ )}
+
+ subject { render_template(source, context) }
+
+ it { is_expected.to eq 'Locomotive built by NoCoffee' }
+ context 'rendering error (action) found in the section' do
+
+ let(:section) { instance_double(
+ 'section',
+ liquid_source: '{% action "Hello world" %}a.b(+}{% endaction %}',
+ definition: {
+ default: 'some default JSON'
+ }
+ )}
+
+ it 'raises ParsingRenderingError' do
+ expect { subject }.to raise_exception(Locomotive::Steam::ParsingRenderingError)
+ end
+ end
+ end
+ end
spec/unit/repositories/section_repository_spec.rb +38 -0
@@ @@ -0,0 +1,38 @@
+ require 'spec_helper'
+ require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb'
+
+ describe Locomotive::Steam::SectionRepository do
+
+ #TODO: site_id should not be passed like this
+ #TODO: template_path should be shorter
+ let(:sections) { [{ name: 'Header', slug: 'header', site_id: 1, template_path: 'spec/fixtures/default/app/views/sections/header.liquid' }] }
+ let(:locale) { :en }
+ let(:site) { instance_double('Site', _id: 1, default_locale: :en, locales: [:en, :fr]) }
+ let(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) }
+ let(:repository) { described_class.new(adapter, site, locale) }
+
+ before do
+ allow(adapter).to receive(:collection).and_return(sections)
+ adapter.cache = NoCacheStore.new
+ end
+
+ describe '#by_slug' do
+
+ let(:name) { nil }
+ subject { repository.by_slug(name) }
+
+ it { is_expected.to eq nil }
+
+ context 'existing section' do
+
+ let(:name) { 'header' }
+ subject { repository.by_slug(name) }
+ it { expect(subject).to_not be_nil }
+ it { expect(subject.class).to eq Locomotive::Steam::Section }
+ it { expect(subject.name).to eq 'Header' }
+ it { expect(subject[:template_path]).to eq 'spec/fixtures/default/app/views/sections/header.liquid' }
+
+ end
+ end
+ end
+
spec/unit/repositories/snippet_repository_spec.rb +0 -3
@@ @@ -1,5 +1,4 @@
require 'spec_helper'
-
require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb'
describe Locomotive::Steam::SnippetRepository do
@@ @@ -31,7 +30,5 @@ describe Locomotive::Steam::SnippetRepository do
it { expect(subject[:template_path][:fr]).to eq 'simple.yml' }
end
-
end
-
end
spec/unit/services/parent_finder_service_spec.rb +1 -2
@@ @@ -35,7 +35,6 @@ describe Locomotive::Steam::ParentFinderService do
it { is_expected.to eq 'Index' }
end
-
end
-
end
+
spec/unit/services/section_finder_service_spec.rb +27 -0
@@ @@ -0,0 +1,27 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::SectionFinderService do
+
+ let(:repository) { instance_double('SectionRepository') }
+ let(:site) { instance_double('Site', _id: 1, default_locale: :en, locales: [:en, :fr])
+ let(:section) { instance_double('Section') }
+ let(:slug) { 'header' }
+
+ let(:finder) { described_class.new repository }
+
+ before do
+ allow(repository).to receive(:by_slug).and_return(section)
+ allow(repository).to receive(:locale).and_return(:en)
+ allow(repository).to receive(:site).and_return(site)
+ allow(section).to receive(:localized_attributes).and_return(nil)
+ end
+
+ describe '#find' do
+
+ subject { finder.find(slug) }
+
+ it { is_expected.to respond_with section }
+
+ end
+ end
+