Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an ability to create custom components #102

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 33 additions & 21 deletions lib/inky.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,58 @@
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well be consistent and use ::Inky...

"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) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transform_values is Ruby 2.4+ only; we support older rubies (I think). Would be easy to require 'backports/2.4.0/hash/transform_values' or change the code.

.with_indifferent_access
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best not use with_indifferent_access which is Rails-only (and controversial)


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)
if html_string.encoding.name == "ASCII-8BIT"
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 =~ /<html/i ? :parse : :fragment
html = Nokogiri::HTML.public_send(parse_cmd, str)
transform_doc(html)
string = html.to_html
string.gsub!(INTERIM_TH_TAG_REGEX, 'th')
Inky::Core.re_inject_raws(string, raws)
::Inky::Core.re_inject_raws(string, raws)
end

def transform_doc(elem)
Expand Down
144 changes: 4 additions & 140 deletions lib/inky/component_factory.rb
Original file line number Diff line number Diff line change
@@ -1,148 +1,12 @@
module Inky
module ComponentFactory
def component_factory(elem)
transform_method = :"_transform_#{component_lookup[elem.name]}"
return unless respond_to?(transform_method)

def component_factory(elem)
component_instance = components[elem.name]
return unless component_instance
inner = elem.children.map(&:to_s).join
send(transform_method, elem, inner)
end

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

# 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)}}

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

def _transform_button(component, inner)
expand = _has_class(component, 'expand')
if component.attr('href')
target = _target_attribute(component)
extra = ' align="center" class="float-center"' if expand
inner = %{<a href="#{component.attr('href')}"#{target}#{extra}>#{inner}</a>}
end
inner = "<center>#{inner}</center>" if expand

classes = _combine_classes(component, 'button')
expander = '<td class="expander"></td>' if expand
%{<table class="#{classes}"><tr><td><table><tr><td>#{inner}</td></tr></table></td>#{expander}</tr></table>}
end

def _transform_menu(component, inner)
attributes = _combine_attributes(component, 'menu')
%{<table #{attributes}><tr><td><table><tr>#{inner}</tr></table></td></tr></table>}
end

def _transform_menu_item(component, inner)
target = _target_attribute(component)
attributes = _combine_attributes(component, 'menu-item')
%{<#{INTERIM_TH_TAG} #{attributes}><a href="#{component.attr('href')}"#{target}>#{inner}</a></#{INTERIM_TH_TAG}>}
component_instance.transform(elem, inner)
end

def _transform_container(component, inner)
attributes = _combine_attributes(component, 'container')
%{<table #{attributes} align="center"><tbody><tr><td>#{inner}</td></tr></tbody></table>}
end

def _transform_row(component, inner)
attributes = _combine_attributes(component, 'row')
%{<table #{attributes}><tbody><tr>#{inner}</tr></tbody></table>}
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 = %{<th class="expander"></th>} if large_size.to_i == column_count && subrows.empty?

%{<#{INTERIM_TH_TAG} class="#{classes}" #{_pass_through_attributes(component)}><table><tr><th>#{inner}</th>#{expander}</tr></table></#{INTERIM_TH_TAG}>}
end

def _transform_block_grid(component, inner)
classes = _combine_classes(component, "block-grid up-#{component.attr('up')}")
%{<table class="#{classes}"><tr>#{inner}</tr></table>}
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)
%{<table #{attributes}class="callout"><tr><th class="#{classes}">#{inner}</th><th class="expander"></th></tr></table>}
end

def _transform_spacer(component, _inner)
classes = _combine_classes(component, 'spacer')
build_table = ->(size, extra) { %{<table class="#{classes} #{extra}"><tbody><tr><td height="#{size}px" style="font-size:#{size}px;line-height:#{size}px;">&#xA0;</td></tr></tbody></table>} }
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')
%{<table #{attributes} align="center"><tr><td class="wrapper-inner">#{inner}</td></tr></table>}
end
end
end
42 changes: 42 additions & 0 deletions lib/inky/components/base.rb
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why these methods started with _ (simili-private?) but if we are to move them we probably should use the standard conventions and remove the leading _. You can make them protected if you like, but I don't feel that would be necessary.

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
14 changes: 14 additions & 0 deletions lib/inky/components/block_grid.rb
Original file line number Diff line number Diff line change
@@ -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')}")
%{<table class="#{classes}"><tr>#{inner}</tr></table>}
end

end
end
end
23 changes: 23 additions & 0 deletions lib/inky/components/button.rb
Original file line number Diff line number Diff line change
@@ -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 = %{<a href="#{component.attr('href')}"#{target}#{extra}>#{inner}</a>}
end
inner = "<center>#{inner}</center>" if expand

classes = _combine_classes(component, 'button')
expander = '<td class="expander"></td>' if expand
%{<table class="#{classes}"><tr><td><table><tr><td>#{inner}</td></tr></table></td>#{expander}</tr></table>}
end

end
end
end
15 changes: 15 additions & 0 deletions lib/inky/components/callout.rb
Original file line number Diff line number Diff line change
@@ -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)
%{<table #{attributes}class="callout"><tr><th class="#{classes}">#{inner}</th><th class="expander"></th></tr></table>}
end

end
end
end
23 changes: 23 additions & 0 deletions lib/inky/components/center.rb
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions lib/inky/components/columns.rb
Original file line number Diff line number Diff line change
@@ -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 !IE]><th class="expander"></th><![endif]-->} if large_size.to_i == inky.column_count && subrows.empty?

%{<#{::Inky::Core::INTERIM_TH_TAG} class="#{classes}" #{_pass_through_attributes(component)}><table><tr><th>#{inner}</th>#{expander}</tr></table></#{::Inky::Core::INTERIM_TH_TAG}>}
end

end
end
end
Loading