# frozen_string_literal: true # Representa los distintos tipos de campos que pueden venir de una # plantilla compleja class Post class TemplateField attr_reader :post, :contents, :key STRING_VALUES = %w[string text url number email password date year image video audio document].freeze # Tipo de valores que son archivos FILE_TYPES = %w[image video audio document].freeze def initialize(post, key, contents) @post = post @key = key @contents = contents end def title contents.dig('title') if complex? end def subtitle contents.dig('subtitle') if complex? end # Obtiene el valor def value complex? ? contents.dig('value') : contents end def max return 0 if simple? contents.fetch('max', 0) end def min return 0 if simple? contents.fetch('min', 0) end # TODO: volver elegante! def type return @type if @type if image? @type = 'image' elsif email? @type = 'email' elsif url? @type = 'url' elsif number? @type = 'number' elsif password? @type = 'password' elsif date? @type = 'date' elsif year? @type = 'year' elsif text_area? @type = 'text_area' elsif check_box_group? @type = 'check_box_group' elsif radio_group? @type = 'radio_group' elsif string? @type = 'text' # TODO: volver a hacer funcionar esto y ahorranos los multiple: # false elsif string? && contents.split('/', 2).count == 2 @type = 'select' elsif nested? @type = 'table' elsif array? @type = 'select' elsif boolean? @type = 'check_box' end @type end # Devuelve los valores vacíos según el tipo def empty_value if string? '' elsif nested? # TODO: devolver las keys también {} elsif array? [] elsif boolean? false end end def cols complex? && contents.dig('cols') end def align complex? && contents.dig('align') end # El campo es requerido si es complejo y se especifica que lo sea def required? complex? && contents.dig('required') end def boolean? value.is_a?(FalseClass) || value.is_a?(TrueClass) end def string? value.is_a? String end def text_area? value == 'text' end def url? value == 'url' end def email? value == 'email' || value == 'mail' end alias mail? email? def date? value == 'date' end def password? value == 'password' end def number? value == 'number' end def year? value == 'year' end def file? string? && FILE_TYPES.include?(value) end def image? array? ? value.first == 'image' : value == 'image' end # Si la plantilla es simple no está admitiendo Hashes como valores def simple? !complex? end def complex? contents.is_a? Hash end # XXX Retrocompatibilidad def to_s key end # Convierte el campo en un parámetro def to_param if nested? { key.to_sym => {} } elsif array? && multiple? { key.to_sym => [] } else key.to_sym end end # Convierte la plantilla en el formato de front_matter def to_front_matter { key => empty_value } end def check_box_group? array? && (complex? && contents.fetch('checkbox', false)) end def radio_group? array? && (complex? && contents.fetch('radio', false)) end def array? value.is_a? Array end # TODO: detectar cuando es complejo y tomar el valor de :multiple def multiple? # si la plantilla es simple, es multiple cuando tenemos un array return array? if simple? array? && contents.fetch('multiple', true) end # Detecta si el valor es una tabla de campos def nested? value.is_a?(Hash) || (array? && value.first.is_a?(Hash)) end # Un campo acepta valores abiertos si no es un array con múltiples # elementos def open? # Todos los valores simples son abiertos return true unless complex? return false unless array? # La cosa se complejiza cuando tenemos valores complejos # # Si tenemos una lista cerrada de valores, necesitamos saber si el # campo es abierto o cerrado. Si la lista tiene varios elementos, # es una lista cerrada, opcionalmente abierta. Si la lista tiene # un elemento, quiere decir que estamos autocompletando desde otro # lado. contents.fetch('open', value.count < 2) end def closed? !open? end # Determina si los valores del campo serán públicos después # # XXX Esto es solo una indicación, el theme Jekyll tiene que # respetarlos por su lado luego def public? # Todos los campos son públicos a menos que se indique lo # contrario simple? || contents.fetch('public', true) end def private? !public? end def human h = key.humanize h end def label h = (complex? && contents.dig('label')) || human h += ' *' if required? h end def help complex? && contents.dig('help') end def nested_fields return unless nested? v = value v = value.first if array? @nested_fields ||= v.map do |k, sv| Post::TemplateField.new post, k, sv end end # Obtiene los valores posibles para el campo de la plantilla def values return 'false' if value == false return 'true' if value == true # XXX por alguna razón `value` no refiere a value() :/ return '' if STRING_VALUES.include? value # Las listas cerradas no necesitan mayor procesamiento return value if array? && closed? && value.count > 1 # Y las vacías tampoco return value if array? && value.empty? # Ahorrarnos el trabajo return @values if @values # Duplicar el valor para no tener efectos secundarios luego (?) value = self.value.dup # Para obtener los valores posibles, hay que procesar la string y # convertirla a parametros # Si es una array de un solo elemento, es un indicador de que # tenemos que rellenarla con los valores que indica. # # El primer valor es el que trae la string de autocompletado values = array? ? value.shift : value # Si el valor es un array con más de un elemento, queremos usar # esas opciones. Pero si además es abierto, queremos traer los # valores cargados anteriormente. # Procesamos el valor, buscando : como separador de campos que # queremos encontrar y luego los unimos _value = (values&.split(':', 2) || []).map do |v| # Tenemos hasta tres niveles de búsqueda collection, attr, subattr = v.split('/', 3) if collection == 'site' # TODO: puede ser peligroso permitir acceder a cualquier # atributo de site? No estamos trayendo nada fuera de # lo normal post.site.send(attr.to_sym) # Si hay un subatributo, tenemos que averiguar todos los # valores dentro de el # TODO volver elegante! # TODO volver recursivo! elsif subattr post.site.everything_of(attr, lang: collection) .compact .map { |sv| sv[subattr] } .flatten .compact .uniq else post.site.everything_of(attr, lang: collection).compact end end # Si el valor es abierto, sumar los valores auto-completados a # lo pre-cargados. # # En este punto _value es un array de 1 o 2 arrays, si es de uno, # value tambien tiene que serlo. Si es de 2, hay que unir cada # una if open? if _value.count == 1 _value = [(_value.first + value).uniq] elsif _value.count == 2 _value = _value.each_with_index.map do |v, i| v + value.fetch(i, []) end end end # Crea un array de arrays, útil para los select # [ [ 1, a ], [ 2, b ] ] # aunque si no hay un : en el autocompletado, el array queda # [ [ 1, 1 ], [ 2, 2 ] ] values = _value.empty? ? [] : _value.last.zip(_value.first) # En última instancia, traer el valor por defecto y ahorrarnos # volver a procesar @values = values end end end