# 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 # 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 case when image? @type = 'image' when email? @type = 'email' when url? @type = 'url' when number? @type = 'number' when password? @type = 'password' when date? @type = 'date' when year? @type = 'year' when text_area? @type = 'text_area' when string? @type = 'text' # TODO volver a hacer funcionar esto y ahorranos los multiple: # false when string? && contents.split('/', 2).count == 2 @type = 'select' when nested? @type = 'table' when array? @type = 'select' when boolean? @type = 'check_box' end @type end # Devuelve los valores vacíos según el tipo def empty_value case when string? '' when nested? # TODO devolver las keys también {} when array? [] when 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 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 = h + ' *' if required? h end def label (complex? && contents.dig('label')) || human 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 self.value == false return 'true' if self.value == true # XXX por alguna razón `value` no refiere a value() :/ return '' if STRING_VALUES.include? self.value # Las listas cerradas no necesitan mayor procesamiento return self.value if array? && closed? && self.value.count > 1 # Y las vacías tampoco return self.value if array? && self.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.try(: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! elsif subattr post.site.everything_of(attr, lang: collection) .compact .map { |sv| sv[subattr] } .flatten .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