diff --git a/lib/inky.rb b/lib/inky.rb index a97bfb6..0c7f563 100644 --- a/lib/inky.rb +++ b/lib/inky.rb @@ -2,32 +2,44 @@ require_relative 'inky/configuration' require_relative 'inky/component_factory' +Dir.glob(File.expand_path("../inky/components/*", __FILE__)).each do |file| + require_relative file +end + module Inky class Core - attr_accessor :components, :column_count, :component_lookup, :component_tags + attr_accessor :components, :column_count, :component_tags + + # These constants are used to circumvent an issue with JRuby Nokogiri. + # For more details see https://github.com/zurb/inky-rb/pull/94 + INTERIM_TH_TAG = 'inky-interim-th'.freeze + INTERIM_TH_TAG_REGEX = %r{(?<=\<|\<\/)#{Regexp.escape(INTERIM_TH_TAG)}} + + DEFAULT_COMPONENTS = { + "button" => Inky::Components::Button, + "row" => Inky::Components::Row, + "inky" => Inky::Components::Inky, + "columns" => Inky::Components::Columns, + "container" => Inky::Components::Container, + "block-grid" => Inky::Components::BlockGrid, + "menu" => Inky::Components::Menu, + "center" => Inky::Components::Center, + "callout" => Inky::Components::Callout, + "spacer" => Inky::Components::Spacer, + "wrapper" => Inky::Components::Wrapper, + "item" => Inky::Components::MenuItem + } include ComponentFactory - def initialize(options = {}) - self.components = { - button: 'button', - row: 'row', - columns: 'columns', - container: 'container', - inky: 'inky', - block_grid: 'block-grid', - menu: 'menu', - center: 'center', - callout: 'callout', - spacer: 'spacer', - wrapper: 'wrapper', - menu_item: 'item' - }.merge(options[:components] || {}) - self.component_lookup = components.invert + def initialize(options = {}) + self.components = DEFAULT_COMPONENTS + .merge(options[:components] || ::Inky.configuration.components) + .transform_values { |component_class| component_class.new(self) } + .with_indifferent_access self.column_count = options[:column_count] || ::Inky.configuration.column_count - - self.component_tags = components.values + self.component_tags = components.keys end def release_the_kraken(html_string) @@ -35,13 +47,13 @@ def release_the_kraken(html_string) html_string.force_encoding('utf-8') # transform_doc barfs if encoding is ASCII-8bit end html_string = html_string.gsub(/doctype/i, 'DOCTYPE') - raws, str = Inky::Core.extract_raws(html_string) + raws, str = ::Inky::Core.extract_raws(html_string) parse_cmd = str =~ /#{inner}} - end - inner = "
#{inner}
" if expand - - classes = _combine_classes(component, 'button') - expander = '' if expand - %{#{expander}
#{inner}
} - end - - def _transform_menu(component, inner) - attributes = _combine_attributes(component, 'menu') - %{
#{inner}
} - end - - def _transform_menu_item(component, inner) - target = _target_attribute(component) - attributes = _combine_attributes(component, 'menu-item') - %{<#{INTERIM_TH_TAG} #{attributes}>#{inner}} + component_instance.transform(elem, inner) end - def _transform_container(component, inner) - attributes = _combine_attributes(component, 'container') - %{
#{inner}
} - end - - def _transform_row(component, inner) - attributes = _combine_attributes(component, 'row') - %{#{inner}
} - end - - # in inky.js this is factored out into makeClumn. TBD if we need that here. - def _transform_columns(component, inner) - col_count = component.parent.elements.size - - small_val = component.attr('small') - large_val = component.attr('large') - - small_size = small_val || column_count - large_size = large_val || small_val || (column_count / col_count).to_i - - classes = _combine_classes(component, "small-#{small_size} large-#{large_size} columns") - - classes << ' first' unless component.previous_element - classes << ' last' unless component.next_element - - subrows = component.elements.css(".row").to_a.concat(component.elements.css("row").to_a) - expander = %{} if large_size.to_i == column_count && subrows.empty? - - %{<#{INTERIM_TH_TAG} class="#{classes}" #{_pass_through_attributes(component)}>#{expander}
#{inner}
} - end - - def _transform_block_grid(component, inner) - classes = _combine_classes(component, "block-grid up-#{component.attr('up')}") - %{#{inner}
} - end - - def _transform_center(component, _inner) - # NOTE: Using children instead of elements because elements.to_a - # sometimes appears to miss elements that show up in size - component.elements.each do |child| - child['align'] = 'center' - child['class'] = _combine_classes(child, 'float-center') - items = component.elements.css(".menu-item").to_a.concat(component.elements.css("item").to_a) - items.each do |item| - item['class'] = _combine_classes(item, 'float-center') - end - end - component.to_s - end - - def _transform_callout(component, inner) - classes = _combine_classes(component, 'callout-inner') - attributes = _pass_through_attributes(component) - %{
#{inner}
} - end - - def _transform_spacer(component, _inner) - classes = _combine_classes(component, 'spacer') - build_table = ->(size, extra) { %{
 
} } - size = component.attr('size') - size_sm = component.attr('size-sm') - size_lg = component.attr('size-lg') - if size_sm || size_lg - html = '' - html << build_table[size_sm, 'hide-for-large'] if size_sm - html << build_table[size_lg, 'show-for-large'] if size_lg - html - else - build_table[size || 16, nil] - end - end - - def _transform_wrapper(component, inner) - attributes = _combine_attributes(component, 'wrapper') - %{
#{inner}
} - end end end diff --git a/lib/inky/components/base.rb b/lib/inky/components/base.rb new file mode 100644 index 0000000..0d506a8 --- /dev/null +++ b/lib/inky/components/base.rb @@ -0,0 +1,42 @@ +module Inky + module Components + class Base + tags = %w[class id href size large no-expander small target] + tags = tags.to_set if tags.respond_to? :to_set + + IGNORED_ON_PASSTHROUGH = tags.freeze + + attr_accessor :inky + + def initialize(inky) + @inky = inky + end + + def _pass_through_attributes(elem) + elem.attributes.reject { |e| IGNORED_ON_PASSTHROUGH.include?(e.downcase) }.map do |name, value| + %{#{name}="#{value}" } + end.join + end + + def _has_class(elem, klass) + elem.attr('class') =~ /(^|\s)#{klass}($|\s)/ + end + + def _combine_classes(elem, extra_classes) + existing = elem['class'].to_s.split(' ') + to_add = extra_classes.to_s.split(' ') + + (existing + to_add).uniq.join(' ') + end + + def _combine_attributes(elem, extra_classes = nil) + classes = _combine_classes(elem, extra_classes) + [_pass_through_attributes(elem), classes && %{class="#{classes}"}].join + end + + def _target_attribute(elem) + elem.attributes['target'] ? %{ target="#{elem.attributes['target']}"} : '' + end + end + end +end diff --git a/lib/inky/components/block_grid.rb b/lib/inky/components/block_grid.rb new file mode 100644 index 0000000..c93adfa --- /dev/null +++ b/lib/inky/components/block_grid.rb @@ -0,0 +1,14 @@ +require_relative "./base" + +module Inky + module Components + class BlockGrid < Base + + def transform(component, inner) + classes = _combine_classes(component, "block-grid up-#{component.attr('up')}") + %{#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/button.rb b/lib/inky/components/button.rb new file mode 100644 index 0000000..df41f90 --- /dev/null +++ b/lib/inky/components/button.rb @@ -0,0 +1,23 @@ +require_relative "./base" + +module Inky + module Components + class Button < Base + + def transform(component, inner) + expand = _has_class(component, 'expand') + if component.attr('href') + target = _target_attribute(component) + extra = ' align="center" class="float-center"' if expand + inner = %{#{inner}} + end + inner = "
#{inner}
" if expand + + classes = _combine_classes(component, 'button') + expander = '' if expand + %{#{expander}
#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/callout.rb b/lib/inky/components/callout.rb new file mode 100644 index 0000000..1b4d3f2 --- /dev/null +++ b/lib/inky/components/callout.rb @@ -0,0 +1,15 @@ +require_relative "./base" + +module Inky + module Components + class Callout < Base + + def transform(component, inner) + classes = _combine_classes(component, 'callout-inner') + attributes = _pass_through_attributes(component) + %{
#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/center.rb b/lib/inky/components/center.rb new file mode 100644 index 0000000..00dfa69 --- /dev/null +++ b/lib/inky/components/center.rb @@ -0,0 +1,23 @@ +require_relative "./base" + +module Inky + module Components + class Center < Base + + def transform(component, _inner) + # NOTE: Using children instead of elements because elements.to_a + # sometimes appears to miss elements that show up in size + component.elements.each do |child| + child['align'] = 'center' + child['class'] = _combine_classes(child, 'float-center') + items = component.elements.css(".menu-item").to_a.concat(component.elements.css("item").to_a) + items.each do |item| + item['class'] = _combine_classes(item, 'float-center') + end + end + component.to_s + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/columns.rb b/lib/inky/components/columns.rb new file mode 100644 index 0000000..bff0c2d --- /dev/null +++ b/lib/inky/components/columns.rb @@ -0,0 +1,30 @@ +require_relative "./base" + +module Inky + module Components + class Columns < Base + + # in inky.js this is factored out into makeClumn. TBD if we need that here. + def transform(component, inner) + col_count = component.parent.elements.size + + small_val = component.attr('small') + large_val = component.attr('large') + + small_size = small_val || inky.column_count + large_size = large_val || small_val || (inky.column_count / col_count).to_i + + classes = _combine_classes(component, "small-#{small_size} large-#{large_size} columns") + + classes << ' first' unless component.previous_element + classes << ' last' unless component.next_element + + subrows = component.elements.css(".row").to_a.concat(component.elements.css("row").to_a) + expander = %{} if large_size.to_i == inky.column_count && subrows.empty? + + %{<#{::Inky::Core::INTERIM_TH_TAG} class="#{classes}" #{_pass_through_attributes(component)}>#{expander}
#{inner}
} + end + + end + end +end diff --git a/lib/inky/components/container.rb b/lib/inky/components/container.rb new file mode 100644 index 0000000..5002caf --- /dev/null +++ b/lib/inky/components/container.rb @@ -0,0 +1,14 @@ +require_relative "./base" + +module Inky + module Components + class Container < Base + + def transform(component, inner) + attributes = _combine_attributes(component, 'container') + %{
#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/inky.rb b/lib/inky/components/inky.rb new file mode 100644 index 0000000..6c515c2 --- /dev/null +++ b/lib/inky/components/inky.rb @@ -0,0 +1,13 @@ +require_relative "./base" + +module Inky + module Components + class Inky < Base + + def transform(component, inner) + inky.release_the_kraken(inner) + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/menu.rb b/lib/inky/components/menu.rb new file mode 100644 index 0000000..de190f9 --- /dev/null +++ b/lib/inky/components/menu.rb @@ -0,0 +1,14 @@ +require_relative "./base" + +module Inky + module Components + class Menu < Base + + def transform(component, inner) + attributes = _combine_attributes(component, 'menu') + %{
#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/menu_item.rb b/lib/inky/components/menu_item.rb new file mode 100644 index 0000000..90593e7 --- /dev/null +++ b/lib/inky/components/menu_item.rb @@ -0,0 +1,15 @@ +require_relative "./base" + +module Inky + module Components + class MenuItem < Base + + def transform(component, inner) + target = _target_attribute(component) + attributes = _combine_attributes(component, 'menu-item') + %{<#{::Inky::Core::INTERIM_TH_TAG} #{attributes}>#{inner}} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/row.rb b/lib/inky/components/row.rb new file mode 100644 index 0000000..3dd6c05 --- /dev/null +++ b/lib/inky/components/row.rb @@ -0,0 +1,14 @@ +require_relative "./base" + +module Inky + module Components + class Row < Base + + def transform(component, inner) + attributes = _combine_attributes(component, 'row') + %{#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/spacer.rb b/lib/inky/components/spacer.rb new file mode 100644 index 0000000..fa76c8e --- /dev/null +++ b/lib/inky/components/spacer.rb @@ -0,0 +1,25 @@ +require_relative "./base" + +module Inky + module Components + class Spacer < Base + + def transform(component, _inner) + classes = _combine_classes(component, 'spacer') + build_table = ->(size, extra) { %{
 
} } + size = component.attr('size') + size_sm = component.attr('size-sm') + size_lg = component.attr('size-lg') + if size_sm || size_lg + html = '' + html << build_table[size_sm, 'hide-for-large'] if size_sm + html << build_table[size_lg, 'show-for-large'] if size_lg + html + else + build_table[size || 16, nil] + end + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/components/wrapper.rb b/lib/inky/components/wrapper.rb new file mode 100644 index 0000000..76b4fb1 --- /dev/null +++ b/lib/inky/components/wrapper.rb @@ -0,0 +1,14 @@ +require_relative "./base" + +module Inky + module Components + class Wrapper < Base + + def transform(component, inner) + attributes = _combine_attributes(component, 'wrapper') + %{
#{inner}
} + end + + end + end +end \ No newline at end of file diff --git a/lib/inky/configuration.rb b/lib/inky/configuration.rb index 90026e3..357945e 100644 --- a/lib/inky/configuration.rb +++ b/lib/inky/configuration.rb @@ -16,6 +16,9 @@ def self.configuration=(config) # Inky.configure do |config| # config.template_engine = :slim # config.column_count = 24 + # config.components = { + # 'custom-block': My::Components::CustomBlock + # } # end # ``` def self.configure @@ -23,11 +26,12 @@ def self.configure end class Configuration - attr_accessor :template_engine, :column_count + attr_accessor :template_engine, :column_count, :components def initialize @template_engine = :erb @column_count = 12 + @components = {} end end end diff --git a/lib/inky/rails/template_handler.rb b/lib/inky/rails/template_handler.rb index bb614c4..c8e8088 100644 --- a/lib/inky/rails/template_handler.rb +++ b/lib/inky/rails/template_handler.rb @@ -20,7 +20,7 @@ def call(template, source = nil) else engine_handler.call(template) end - "Inky::Core.new.release_the_kraken(begin; #{compiled_source};end)" + "::Inky::Core.new.release_the_kraken(begin; #{compiled_source};end)" end module Composer diff --git a/spec/components_spec.rb b/spec/components_spec.rb index 848ed2c..d89a178 100644 --- a/spec/components_spec.rb +++ b/spec/components_spec.rb @@ -357,7 +357,7 @@ expected = "<>" # Can't do vanilla compare because the second will fail to parse - inky = Inky::Core.new + inky = ::Inky::Core.new output = inky.release_the_kraken(input) expect(output).to eql(expected) end diff --git a/spec/inky_spec.rb b/spec/inky_spec.rb index 4ba854d..8f5c11c 100644 --- a/spec/inky_spec.rb +++ b/spec/inky_spec.rb @@ -15,7 +15,7 @@ HTML - output = Inky::Core.new.release_the_kraken(input) + output = ::Inky::Core.new.release_the_kraken(input) expect_same_html(output, expected) end @@ -31,7 +31,7 @@ HTML - output = Inky::Core.new.release_the_kraken(input) + output = ::Inky::Core.new.release_the_kraken(input) expect_same_html(output, expected) end @@ -47,7 +47,7 @@ HTML - output = Inky::Core.new.release_the_kraken(input) + output = ::Inky::Core.new.release_the_kraken(input) expect_same_html(output, expected) output.encoding.name.should == 'US-ASCII' end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6988620..0589742 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,8 +23,12 @@ def expect_same_html(input, expected) expect(reformat_html(input)).to eql(reformat_html(expected)) end +def expect_includes_html(input, expected) + expect(reformat_html(input)).to include(reformat_html(expected)) +end + def compare(input, expected) - inky = Inky::Core.new + inky = ::Inky::Core.new output = inky.release_the_kraken(input) expect_same_html(output, expected) end diff --git a/spec/test_app/app/views/inky/custom_block.html.inky b/spec/test_app/app/views/inky/custom_block.html.inky new file mode 100644 index 0000000..65d3ac4 --- /dev/null +++ b/spec/test_app/app/views/inky/custom_block.html.inky @@ -0,0 +1 @@ +Hello from custom block! \ No newline at end of file diff --git a/spec/test_app/app/views/inky/custom_block_with_nested_blocks.html.inky b/spec/test_app/app/views/inky/custom_block_with_nested_blocks.html.inky new file mode 100644 index 0000000..91396c2 --- /dev/null +++ b/spec/test_app/app/views/inky/custom_block_with_nested_blocks.html.inky @@ -0,0 +1 @@ +Hello from custom block! \ No newline at end of file diff --git a/spec/test_app/lib/components/custom_block.rb b/spec/test_app/lib/components/custom_block.rb new file mode 100644 index 0000000..ac2676a --- /dev/null +++ b/spec/test_app/lib/components/custom_block.rb @@ -0,0 +1,9 @@ +module Components + class CustomBlock < Inky::Components::Base + + def transform(component, inner) + %{#{inner}} + end + + end +end \ No newline at end of file diff --git a/spec/test_app/lib/components/custom_block_with_nested_blocks.rb b/spec/test_app/lib/components/custom_block_with_nested_blocks.rb new file mode 100644 index 0000000..ccf7e73 --- /dev/null +++ b/spec/test_app/lib/components/custom_block_with_nested_blocks.rb @@ -0,0 +1,9 @@ +module Components + class CustomBlockWithNestedBlocks < Inky::Components::Base + + def transform(component, inner) + inky.release_the_kraken(%{}) + end + + end +end \ No newline at end of file diff --git a/spec/test_app/spec/features/inky_spec.rb b/spec/test_app/spec/features/inky_spec.rb index baf6b19..3a64111 100644 --- a/spec/test_app/spec/features/inky_spec.rb +++ b/spec/test_app/spec/features/inky_spec.rb @@ -1,4 +1,6 @@ require_relative '../helper' +require_relative '../../lib/components/custom_block' +require_relative '../../lib/components/custom_block_with_nested_blocks' def simple_container(text) <<-HTML @@ -97,4 +99,38 @@ def simple_container(text) expect_same_html page.html, simple_container('Built with builder') end end + + context "when configured to use a custom component" do + around do |spec| + Inky.configure do |config| + old = config.components + config.components = { + 'custom-block': Components::CustomBlock, + 'custom-block-with-nested-blocks': Components::CustomBlockWithNestedBlocks + } + spec.run + config.components = old + end + end + + it "works with the basic custom components" do + visit "/inky/custom_block" + + expect_includes_html page.html, <<-HTML + Hello from custom block! + HTML + end + + it "works with the basic custom components" do + visit "/inky/custom_block_with_nested_blocks" + + expect_includes_html page.html, <<-HTML + + HTML + + expect_includes_html page.html, <<-HTML + + HTML + end + end end
Hello from custom block!