require "raconteur"
require "kramdown"
require "nokogiri"
require "anecdote/engine"
module Anecdote
def self.markdown_and_parse(content="")
markdown_only( raconteur.parse(content) )
end
def self.markdown_only(content)
::Kramdown::Document.new( content.presence || "", { input: :GFM } ).to_html.html_safe
end
def self.raconteur
@raconteur ||= ::Raconteur.new
end
def self.init_raconteur
raconteur.settings.setting_quotes = '$'
raconteur.processors.register!('graphic', {
handler: lambda do |settings|
klasses = (['anecdote-graphic-dn32ja'] + module_classes(settings))
klasses << case settings[:border]
when 'none' then 'v-border-none'
when 'shadow' then 'v-border-shadow'
when 'line' then 'v-border-line'
end
klasses << 'v-sidebar-caption' if [true, 'true', 'yes', 'left', 'right', 'first', 'last'].include?(settings[:sidebar_caption])
klasses << 'v-sidebar-caption v-sidebar-caption-left' if ['left', 'first'].include?(settings[:sidebar_caption])
klasses << 'v-sidebar-caption v-sidebar-caption-right' if ['right', 'last'].include?(settings[:sidebar_caption])
contents = []
contents << view_context.content_tag((settings[:href].present? ? :a : :div), (settings[:href].present? ? { href: settings[:href], title: settings[:href_title] } : {}).merge({ class: 'anecdote-intrinsic-embed-n42ha1' })) do
if settings[:assets_path]
content = view_context.image_tag(settings[:assets_path], alt: '')
paperclip_geo = Paperclip::Geometry.from_file(Rails.root.join('app', 'assets', 'images', settings[:assets_path]))
geo = { width: paperclip_geo.width, height: paperclip_geo.height }
elsif settings[:image_url]
content = view_context.image_tag(settings[:image_url], alt: '')
dimensions = (settings[:dimensions] || settings[:image_dimensions]).split('x').map(&:to_f)
geo = { width: dimensions.first, height: dimensions.last }
elsif settings[:embed_code]
content = settings[:embed_code].html_safe
dimensions = settings[:dimensions].split('x').map(&:to_f)
geo = { width: dimensions.first, height: dimensions.last }
end
if settings[:_scope_].present? && settings[:_scope_][:processor].tag == 'gallery'
settings[:_scope_][:settings][:_graphics_] ||= []
settings[:_scope_][:settings][:_graphics_] << geo
end
view_context.content_tag(:div, content, class: 'inner', style: "padding-bottom: #{geo[:height] / geo[:width] * 100}%;")
end
if settings[:caption].present?
contents << view_context.content_tag(:div, view_context.content_tag(:div, markdown_and_parse(settings[:caption]), class: 'inner anecdote-wysicontent-ndj4ab'), class: 'anecdote-caption-ajkd3b')
end
view_context.content_tag(:div, view_context.content_tag(:div, contents.join("\n").html_safe, class: 'inner'), module_wrapper_options(settings).merge(class: klasses.compact.flatten.join(' ')))
end
})
raconteur.processors.register!('gallery', {
handler: lambda do |settings|
klasses = ['anecdote-gallery-dn2bak']
klasses += module_classes(settings)
graphics = settings[:_yield_].html_safe
# handle scaling
flexes = []
if settings[:flexes]
flexes = parse_custom_flexes(settings)
else
if settings[:scale_by] == 'relative-width-bottom-alignment'
# images scaled based on their relative width with bottom alignment
total_width = settings[:_graphics_].sum { |hsh| hsh[:width]}
settings[:_graphics_].each do |graphic|
flex = {
width: graphic[:width] / total_width,
graphic: graphic,
ratio: graphic[:width] / graphic[:height],
gfx_height_pad: graphic[:height] / graphic[:width]
}
flex[:width_ratio_balance] = flex[:width] / flex[:ratio]
flexes << flex
end
tallest = flexes.sort_by { |k| k[:width_ratio_balance] }.last
flexes.map do |flex|
flex[:faux] = flex[:width_ratio_balance] / tallest[:width_ratio_balance]
flex[:gfx_height_pad_faux] = flex[:gfx_height_pad] / flex[:faux]
flex[:top_offset] = flex[:gfx_height_pad_faux] - flex[:gfx_height_pad]
end
index = 0
graphics.gsub!('
') do |match|
flex = flexes[index]
index += 1
(view_context.content_tag(:div, '', class: 'anecdote-flex-offset-a4j2aj', style: "padding-top: #{flex[:top_offset] * 100}%;").html_safe + match.html_safe).html_safe
end
# graphics.gsub!(/anecdote-intrinsic-embed-n42ha1.*?padding-bottom:\s*([\d|\.]*)/mi) do |match|
# flex = flexes[index]
# index += 1
# match.sub(/[\d|\.]*$/, (flex[:gfx_height_pad_faux] * 100).to_s).html_safe
# end
elsif settings[:scale_by] == 'relative-width'
# images scaled based on their relative width
total_width = settings[:_graphics_].sum { |hsh| hsh[:width]}
settings[:_graphics_].each do |graphic|
flexes << { width: graphic[:width] / total_width, graphic: graphic }
end
else
# images scaled to equal height
total_ratio = settings[:_graphics_].sum { |geo| geo[:width] / geo[:height] }
settings[:_graphics_].each do |graphic|
flexes << { width: (graphic[:width] / graphic[:height]) / total_ratio, graphic: graphic }
end
end
end
graphics = insert_flex_basis_styles(graphics, flexes)
# build HTML output
contents = []
settings[:gutter_spacing] = 'small' if settings[:gutter_spacing].blank?
contents << view_context.content_tag(:div, graphics.html_safe, class: (['content'] + flex_classes(settings)).flatten.join(' '))
if settings[:caption].present?
contents << view_context.content_tag(:div, view_context.content_tag(:div, markdown_and_parse(settings[:caption]), class: 'inner anecdote-wysicontent-ndj4ab'), class: 'anecdote-caption-ajkd3b')
end
view_context.content_tag(:div, view_context.content_tag(:div, contents.join("\n").html_safe, class: 'inner'), module_wrapper_options(settings).merge(class: klasses.flatten.join(' ')))
end
})
raconteur.processors.register!('columns', {
handler: lambda do |settings|
klasses = ['anecdote-columns-nab3a2']
klasses += module_classes(settings)
columns = insert_flex_basis_styles(settings[:_yield_].html_safe, parse_custom_flexes(settings))
view_context.content_tag(:div, view_context.content_tag(:div, columns.html_safe, class: (['inner'] + flex_classes(settings)).flatten.join(' ')), module_wrapper_options(settings).merge(class: klasses.flatten.join(' ')))
end
})
raconteur.processors.register!('anecdote', {
handler: lambda do |settings|
klass = (['anecdote-inception-ab2a8j'] + module_classes(settings)).flatten.join(' ')
inner_klass = ['anecdote-wysicontent-ndj4ab', 'inner']
inner_klass << 'v-fit-content-to-fill-container' if settings[:fit_content_to_container].present? && ['true', 'yes', true].include?(settings[:fit_content_to_container])
view_context.content_tag(:div, view_context.content_tag(:div, markdown_and_parse(settings[:_yield_]), class: inner_klass.join(' ')), module_wrapper_options(settings).merge(class: klass))
end
})
raconteur.processors.register!('pull-quote', {
handler: lambda do |settings|
klass = (['anecdote-pull-quote-sba2ha'] + module_classes(settings)).flatten.join(' ')
view_context.content_tag(:div, view_context.content_tag(:div, markdown_and_parse(settings[:text]), class: 'inner'), module_wrapper_options(settings).merge(class: klass))
end
})
raconteur.processors.register!('horizontal-line', {
handler: lambda do |settings|
klasses = ['anecdote-horizontal-line-asj31a']
klasses += module_classes(settings)
klasses << case settings[:weight]
when 'light' then 'v-light'
when 'notable' then 'v-notable'
when 'heavy' then 'v-heavy'
end
view_context.content_tag(:div, '
'.html_safe, module_wrapper_options(settings).merge(class: klasses.flatten.join(' ')))
end
})
raconteur.processors.register!('spacing', {
handler: lambda do |settings|
klasses = ['anecdote-spacing-an4a2q']
klasses << case settings[:size]
when 'tiny' then 'v-tiny'
when 'small' then 'v-small'
when 'standard' then 'v-standard'
when 'big' then 'v-big'
when 'mega' then 'v-mega'
end
view_context.content_tag(:div, nil, module_wrapper_options(settings).merge(class: klasses.flatten.join(' ')))
end
})
raconteur.processors.register!('title', {
handler: lambda do |settings|
klasses = ['anecdote-title-an4a2q']
klasses += spacing_classes(settings)
klasses += text_classes(settings.merge(skip_font_size: true))
supported_sizes = %w(h1 h2 h3 h4 h5 h6 p)
# visual hierarchy, i.e. determines CSS / rendering
if supported_sizes.include?(settings[:size])
klasses << "v-size-#{settings[:size]}"
else
tag = 'v-size-h1'
end
# semantic hierarchy, i.e. determines HTML tag (if undefined, fall back on visual hierarchy)
if settings[:tag].present?
tag = settings[:tag]
elsif supported_sizes.include?(settings[:size])
tag = settings[:size]
else
tag = 'h1'
end
view_context.content_tag(tag, markdown_and_parse_without_wrapping_tags(settings[:text] || settings[:_yield_]), module_wrapper_options(settings).merge(id: settings[:anchor], class: klasses.flatten.join(' ')))
end
})
end
def self.parse_custom_flexes(settings)
ratios = settings[:flexes].split(':').map(&:to_i)
sum = ratios.inject(&:+)
flexes = ratios.map { |ratio| { width: ratio.to_f / sum } }
end
def self.insert_flex_basis_styles(html_content, flexes)
index = 0
doc = ::Nokogiri::HTML::DocumentFragment.parse(html_content)
doc.elements.each do |element|
if element.attributes['class'].present? && element.attributes['class'].value.split(' ').include?('anecdote-module-3ba83n')
flex = flexes[index]
index += 1
styles = []
if flex[:width].present?
styles << "flex-basis:#{flex[:width] * 100}%"
end
element.set_attribute('style', styles.join(';'))
end
end
doc.to_html
end
def self.flex_classes(settings)
klasses = %w(anecdote-flex-module-a4j2aj)
# custom gutter spacing
klasses << case settings[:gutter_spacing]
when 'small' then 'v-gutter-spacing-small'
when 'medium' then 'v-gutter-spacing-medium'
when 'large' then 'v-gutter-spacing-large'
end
# reverse order
if settings.key?(:reverse_order_on_flow) && ['true', 'yes', true].include?(settings[:reverse_order_on_flow])
klasses << 'v-reverse-order-on-flow'
end
# when to wrap
klasses << case settings[:flow_from]
when 'always' then 'v-flow-from-always'
when 'medium-handheld' then 'v-flow-from-medium-handheld'
when 'large-handheld' then 'v-flow-from-large-handheld'
when 'tablet' then 'v-flow-from-tablet'
when 'laptop' then 'v-flow-from-laptop'
when 'large-monitor' then 'v-flow-from-large-monitor'
else
if settings[:size].present?
case settings[:size]
when 'small' then 'v-flow-from-medium-handheld'
when 'medium' then 'v-flow-from-large-handheld'
when 'big' then 'v-flow-from-tablet'
when 'full-width' then 'v-flow-from-tablet'
end
else
'v-flow-from-tablet' # default
end
end
klasses
end
def self.spacing_classes(settings)
klasses = []
css_base = 'anecdote-adhoc-spacing-an4a2q'
css = {
none: 'an4a2q-v-none',
tiny: 'an4a2q-v-tiny',
small: 'an4a2q-v-small',
standard: 'an4a2q-v-standard',
big: 'an4a2q-v-big',
mega: 'an4a2q-v-mega'
}
supported_sizes = css.keys.map(&:to_s)
# both top and bottom
if settings[:spacing].present? && supported_sizes.include?(settings[:spacing])
klasses << css_base
klasses << ["#{css[settings[:spacing].to_sym]}-top", "#{css[settings[:spacing].to_sym]}-bottom"]
end
# top only
if settings[:spacing_before].present? && supported_sizes.include?(settings[:spacing_before])
klasses << css_base
klasses << "#{css[settings[:spacing_before].to_sym]}-top"
end
# bottom only
if settings[:spacing_after].present? && supported_sizes.include?(settings[:spacing_after])
klasses << css_base
klasses << "#{css[settings[:spacing_after].to_sym]}-bottom"
end
# flatten (in case of generic setting, which produces a nested array)
# uniq (in case of individual top and bottom settings)
klasses.flatten.compact.uniq
end
def self.text_classes(settings)
klasses = []
klasses << case settings[:font_family]
when 'primary' then 'anecdote-primary-font-a3a8fb'
when 'secondary' then 'anecdote-secondary-font-a3a8fb'
end
klasses << case settings[:text_dimming]
when 'none' then 'anecdote-no-text-dimming-lk8j2n'
when 'mild' then 'anecdote-mild-text-dimming-lk8j2n'
when 'medium' then 'anecdote-medium-text-dimming-lk8j2n'
when 'aggressive' then 'anecdote-aggressive-text-dimming-lk8j2n'
end
unless settings[:skip_font_size]
klasses << case settings[:font_size]
when 'tiny' then 'anecdote-tiny-text-size-an43ja'
when 'small' then 'anecdote-small-text-size-an43ja'
when 'normal' then 'anecdote-normal-text-size-an43ja'
when 'large' then 'anecdote-large-text-size-an43ja'
end
end
klasses << case settings[:text_align]
when 'left' then 'anecdote-left-aligned-text-vnd5b3'
when 'right' then 'anecdote-right-aligned-text-vnd5b3'
when 'center' then 'anecdote-center-aligned-text-vnd5b3'
end
klasses.flatten.compact.uniq
end
def self.module_wrapper_options(settings)
options = {}
options['data-anecdote-embeddable-image-url'] = settings[:embeddable_image_url] if settings[:embeddable_image_url].present?
options
end
def self.module_classes(settings)
klasses = %w(anecdote-module-3ba83n)
klasses += settings[:css_class].split(' ') if settings[:css_class].present?
klasses += spacing_classes(settings)
klasses += text_classes(settings)
if settings[:size].present?
klasses << case settings[:size]
when 'small' then 'v-size-small'
when 'medium' then 'v-size-medium'
when 'big' then 'v-size-big'
when 'full-width' then 'v-size-full-width'
end
end
if settings[:float].present?
klasses << case settings[:float]
when 'right' then 'v-float-right'
when 'left' then 'v-float-left'
end
klasses << case settings[:float_from]
when 'always' then 'v-float-from-always'
when 'medium-handheld' then 'v-float-from-medium-handheld'
when 'large-handheld' then 'v-float-from-large-handheld'
when 'tablet' then 'v-float-from-tablet'
when 'laptop' then 'v-float-from-laptop'
when 'large-monitor' then 'v-float-from-large-monitor'
else
if settings[:size].present?
case settings[:size]
when 'small' then 'v-float-from-large-handheld'
when 'medium' then 'v-float-from-laptop'
when 'big' then 'v-flow-from-large-monitor'
end
else
'v-float-from-laptop' # default
end
end
end
if settings[:mood].present?
klasses << case settings[:mood]
when 'positive' then 'v-mood-positive'
when 'negative' then 'v-mood-negative'
end
end
klasses.flatten.compact.uniq
end
def self.markdown_and_parse_without_wrapping_tags(text="")
markdown_and_parse(text).gsub(/^\<\/?\w+>|\<\/?\w+>$/i, '').gsub(/^\s*|\s*$/mi, '').html_safe
end
private
def self.view_context
Anecdote::ApplicationController.helpers
end
end
Anecdote.init_raconteur