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 |
| + | |