diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c83b2894..ba92f626 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,7 +2,7 @@ # Helpers module ApplicationHelper - BRACKETS = /[\[\]]/ + BRACKETS = /[\[\]]/.freeze # Devuelve el atributo name de un campo anidado en el formato que # esperan los helpers *_field diff --git a/app/models/metadata_array.rb b/app/models/metadata_array.rb index 368aa546..a8a6e555 100644 --- a/app/models/metadata_array.rb +++ b/app/models/metadata_array.rb @@ -19,8 +19,12 @@ class MetadataArray < MetadataTemplate true && !private? end + def titleize? + true + end + def to_s - value.join(', ') + value.select(&:present?).join(', ') end # Obtiene el valor desde el documento, convirtiéndolo a Array si no lo diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index 17ecaee8..bb666cb9 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -13,6 +13,10 @@ class MetadataBelongsTo < MetadataRelatedPosts '' end + def to_s + belongs_to.try(:title).try(:value).to_s + end + # Obtiene el valor desde el documento. # # @return [String] @@ -107,6 +111,6 @@ class MetadataBelongsTo < MetadataRelatedPosts end def sanitize(uuid) - uuid.to_s.gsub(/[^a-f0-9\-]/i, '') + uuid.to_s.gsub(/[^a-f0-9-]/i, '') end end diff --git a/app/models/metadata_predefined_value.rb b/app/models/metadata_predefined_value.rb index 9cf36382..dbdb1e48 100644 --- a/app/models/metadata_predefined_value.rb +++ b/app/models/metadata_predefined_value.rb @@ -10,6 +10,10 @@ class MetadataPredefinedValue < MetadataString @values ||= layout.dig(:metadata, name, 'values', I18n.locale.to_s)&.invert || {} end + def to_s + values.invert[value].to_s + end + private # Solo permite almacenar los valores predefinidos. diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 2728020e..6ca09f7d 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -22,6 +22,10 @@ class MetadataRelatedPosts < MetadataArray false end + def titleize? + false + end + def indexable_values posts.where(uuid: value).map(&:title).map(&:value) end @@ -41,12 +45,12 @@ class MetadataRelatedPosts < MetadataArray end def title(post) - "#{post&.title&.value || post&.slug&.value} #{post&.date&.value.strftime('%F')} (#{post.layout.humanized_name})" + "#{post&.title&.value || post&.slug&.value} #{post&.date&.value&.strftime('%F')} (#{post.layout.humanized_name})" end def sanitize(uuid) super(uuid.map do |u| - u.to_s.gsub(/[^a-f0-9\-]/i, '') + u.to_s.gsub(/[^a-f0-9-]/i, '') end) end end diff --git a/app/models/metadata_slug.rb b/app/models/metadata_slug.rb index b0fe8cec..417cfa0b 100644 --- a/app/models/metadata_slug.rb +++ b/app/models/metadata_slug.rb @@ -25,7 +25,7 @@ require 'jekyll/utils' class MetadataSlug < MetadataTemplate # Trae el slug desde el título si existe o una string al azar def default_value - title ? Jekyll::Utils.slugify(title, mode: site.slugify_mode) : SecureRandom.uuid + Jekyll::Utils.slugify(title || SecureRandom.uuid, mode: site.slugify_mode) end def value diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb index c1d888b1..cb3ad264 100644 --- a/app/models/metadata_string.rb +++ b/app/models/metadata_string.rb @@ -11,6 +11,10 @@ class MetadataString < MetadataTemplate true && !private? end + def titleize? + true + end + private # No se permite HTML en las strings diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index c762220e..29391ac6 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -16,6 +16,11 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, false end + # El valor puede ser parte de un título auto-generado + def titleize? + false + end + def inspect "#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>" end diff --git a/app/models/metadata_text.rb b/app/models/metadata_text.rb index 103bcd0a..3ac336bb 100644 --- a/app/models/metadata_text.rb +++ b/app/models/metadata_text.rb @@ -1,4 +1,8 @@ # frozen_string_literal: true # Un campo de texto largo -class MetadataText < MetadataString; end +class MetadataText < MetadataString + def titleize? + false + end +end diff --git a/app/models/metadata_title.rb b/app/models/metadata_title.rb new file mode 100644 index 00000000..c913878b --- /dev/null +++ b/app/models/metadata_title.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# El título es obligatorio para todos los Post, si el esquema no lo +# incluye, tenemos que poder generar un valor legible por humanes. +class MetadataTitle < MetadataString + # Obtener todos los valores de texto del artículo y generar un título + # en base a eso. + # + # @return [String] + def default_value + @default_value ||= + post.attributes.select do |attr| + post[attr].titleize? + end.map do |attr| + post[attr].to_s + end.compact.join(' ').strip.squeeze(' ') + end +end diff --git a/app/models/post.rb b/app/models/post.rb index 95ca9a66..f141bc92 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -12,9 +12,21 @@ class Post DEFAULT_ATTRIBUTES = %i[site document layout].freeze # Otros atributos que no vienen en los metadatos PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze - PUBLIC_ATTRIBUTES = %i[lang date uuid created_at].freeze + PUBLIC_ATTRIBUTES = %i[title lang date uuid created_at].freeze + ALIASED_ATTRIBUTES = %i[locale].freeze ATTR_SUFFIXES = %w[? =].freeze + ATTRIBUTE_DEFINITIONS = { + 'title' => { 'type' => 'title', 'required' => true }, + 'lang' => { 'type' => 'lang', 'required' => true }, + 'date' => { 'type' => 'document_date', 'required' => true }, + 'uuid' => { 'type' => 'uuid', 'required' => true }, + 'created_at' => { 'type' => 'created_at', 'required' => true }, + 'slug' => { 'type' => 'slug', 'required' => true }, + 'path' => { 'type' => 'path', 'required' => true }, + 'locale' => { 'alias' => 'lang' } + }.freeze + class PostError < StandardError; end class UnknownAttributeError < PostError; end @@ -53,10 +65,12 @@ class Post @layout = args[:layout] @site = args[:site] @document = args[:document] - @attributes = layout.attributes + PUBLIC_ATTRIBUTES + @attributes = (layout.attributes + PUBLIC_ATTRIBUTES).uniq @errors = {} @metadata = {} + layout.metadata = ATTRIBUTE_DEFINITIONS.merge(layout.metadata).with_indifferent_access + # Leer el documento si existe # @todo Asignar todos los valores a self[:value] luego de leer document&.read! unless new? @@ -131,6 +145,7 @@ class Post src = element.attributes['src'] next unless src&.value&.start_with? 'public/' + file = MetadataFile.new(site: site, post: self, document: document, layout: layout) file.value['path'] = src.value @@ -192,13 +207,13 @@ class Post def method_missing(name, *_args) # Limpiar el nombre del atributo, para que todos los ayudantes # reciban el método en limpio - unless attribute? name - raise NoMethodError, I18n.t('exceptions.post.no_method', method: name) - end + raise NoMethodError, I18n.t('exceptions.post.no_method', method: name) unless attribute? name define_singleton_method(name) do template = layout.metadata[name.to_s] + return public_send(template['alias'].to_sym) if template.key?('alias') + @metadata[name] ||= MetadataFactory.build(document: document, post: self, @@ -214,43 +229,6 @@ class Post public_send name end - # TODO: Mover a method_missing - def slug - @metadata[:slug] ||= MetadataSlug.new(document: document, site: site, layout: layout, name: :slug, type: :slug, - post: self, required: true) - end - - # TODO: Mover a method_missing - def date - @metadata[:date] ||= MetadataDocumentDate.new(document: document, site: site, layout: layout, name: :date, - type: :document_date, post: self, required: true) - end - - # TODO: Mover a method_missing - def path - @metadata[:path] ||= MetadataPath.new(document: document, site: site, layout: layout, name: :path, type: :path, - post: self, required: true) - end - - # TODO: Mover a method_missing - def lang - @metadata[:lang] ||= MetadataLang.new(document: document, site: site, layout: layout, name: :lang, type: :lang, - post: self, required: true) - end - - alias locale lang - - # TODO: Mover a method_missing - def uuid - @metadata[:uuid] ||= MetadataUuid.new(document: document, site: site, layout: layout, name: :uuid, type: :uuid, - post: self, required: true) - end - - # La fecha de creación inmodificable del post - def created_at - @metadata[:created_at] ||= MetadataCreatedAt.new(document: document, site: site, layout: layout, name: :created_at, type: :created_at, post: self, required: true) - end - # Devuelve los strong params para el layout. # # XXX: Nos gustaría no tener que instanciar Metadata acá, pero depende @@ -427,7 +405,8 @@ class Post def attribute?(mid) included = DEFAULT_ATTRIBUTES.include?(mid) || PRIVATE_ATTRIBUTES.include?(mid) || - PUBLIC_ATTRIBUTES.include?(mid) + PUBLIC_ATTRIBUTES.include?(mid) || + ALIASED_ATTRIBUTES.include?(mid) included = attributes.include? mid if !included && singleton_class.method_defined?(:attributes) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index db118d6c..aceeae32 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -185,24 +185,24 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # Si les usuaries modifican o crean una licencia, considerarla # personalizada en el panel. def update_site_license! - if site.usuarie?(usuarie) && post.layout.name == :license && !site.licencia.custom? - site.update licencia: Licencia.find_by_icons('custom') - end + return unless site.usuarie?(usuarie) && post.layout.name == :license && !site.licencia.custom? + + site.update licencia: Licencia.find_by_icons('custom') end # Encuentra todos los posts anidados y los crea o modifica def create_nested_posts!(post, params) post.nested_attributes.each do |nested_attribute| - nested_metadata = post[nested_attribute] - # @todo find_or_initialize - nested_post = nested_metadata.has_one || site.posts(lang: post.lang.value).build(layout: nested_metadata.nested) - nested_params = params.require(nested_attribute).permit(nested_post.params).to_hash + nested_metadata = post[nested_attribute] + # @todo find_or_initialize + nested_post = nested_metadata.has_one || site.posts(lang: post.lang.value).build(layout: nested_metadata.nested) + nested_params = params.require(nested_attribute).permit(nested_post.params).to_hash - # Completa la relación 1:1 - nested_params[nested_metadata.inverse.to_s] = post.uuid.value - post[nested_attribute].value = nested_post.uuid.value + # Completa la relación 1:1 + nested_params[nested_metadata.inverse.to_s] = post.uuid.value + post[nested_attribute].value = nested_post.uuid.value - files << nested_post.path.absolute if nested_post.update(nested_params) + files << nested_post.path.absolute if nested_post.update(nested_params) end end end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 65d3f777..eaa15eb4 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -25,7 +25,6 @@ = render 'layouts/breadcrumb' = render 'layouts/flash' - = yield(:post_form) = yield - if flash[:js] diff --git a/app/views/posts/_attributes.haml b/app/views/posts/_attributes.haml index d3c205dc..03887866 100644 --- a/app/views/posts/_attributes.haml +++ b/app/views/posts/_attributes.haml @@ -11,7 +11,7 @@ - cache [metadata, I18n.locale] do = render("posts/attributes/#{type}", - base: base, post: post, attribute: attribute, - metadata: metadata, site: site, - dir: dir, locale: locale, - autofocus: (post.attributes.first == attribute)) + base: base, post: post, attribute: attribute, + metadata: metadata, site: site, + dir: dir, locale: locale, + autofocus: (post.attributes.first == attribute)) diff --git a/app/views/posts/_attributes_nested.haml b/app/views/posts/_attributes_nested.haml index 83bcc51c..5036b9c7 100644 --- a/app/views/posts/_attributes_nested.haml +++ b/app/views/posts/_attributes_nested.haml @@ -9,10 +9,11 @@ - next if attribute == :date - next if attribute == :draft - next if attribute == inverse + - metadata = post[attribute] - cache [post, metadata, I18n.locale] do = render "posts/attributes/#{metadata.type}", - base: base, post: post, attribute: attribute, - metadata: metadata, site: site, - dir: dir, locale: locale, autofocus: false + base: base, post: post, attribute: attribute, + metadata: metadata, site: site, + dir: dir, locale: locale, autofocus: false diff --git a/app/views/posts/_form.haml b/app/views/posts/_form.haml index 92bee939..8c006274 100644 --- a/app/views/posts/_form.haml +++ b/app/views/posts/_form.haml @@ -33,8 +33,7 @@ - dir = t("locales.#{@locale}.dir") -# Comienza el formulario -= form_tag url, method: method, class: 'form post ' + extra_class, multipart: true do - += form_tag url, method: method, class: "form post #{extra_class}", multipart: true do -# Botones de guardado = render 'posts/submit', site: site, post: post @@ -45,3 +44,6 @@ -# Botones de guardado = render 'posts/submit', site: site, post: post + +-# Formularios usados por los modales += yield(:post_form) diff --git a/app/views/posts/_htmx_form.haml b/app/views/posts/_htmx_form.haml index cfc4071a..a16e68dd 100644 --- a/app/views/posts/_htmx_form.haml +++ b/app/views/posts/_htmx_form.haml @@ -13,7 +13,7 @@ end options = { - id: base, + id: base, multipart: true, class: 'form post ', 'hx-swap': params.require(:swap), @@ -40,3 +40,5 @@ -# Dibuja cada atributo = render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale, except: except + += yield(:post_form) diff --git a/app/views/posts/attribute_ro/_title.haml b/app/views/posts/attribute_ro/_title.haml new file mode 100644 index 00000000..67642e2c --- /dev/null +++ b/app/views/posts/attribute_ro/_title.haml @@ -0,0 +1,3 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale }= metadata.value diff --git a/app/views/posts/attributes/_title.haml b/app/views/posts/attributes/_title.haml new file mode 100644 index 00000000..e69de29b diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index 13607894..e636aa4a 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -4,7 +4,7 @@ .col-12.col-lg-8 %article.content.table-responsive-md = link_to t('posts.edit_post'), - edit_site_post_path(@site, @post.id), - class: 'btn btn-secondary btn-block' + edit_site_post_path(@site, @post.id), + class: 'btn btn-secondary btn-block' = render 'table', dir: dir, site: @site, locale: @locale, post: @post, title: t('.front_matter') diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 6861da45..024a26ab 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -126,7 +126,8 @@ module Jekyll unless spec I18n.with_locale(locale) do - raise Jekyll::Errors::InvalidThemeName, I18n.t('activerecord.errors.models.site.attributes.design_id.missing_gem', theme: name) + raise Jekyll::Errors::InvalidThemeName, + I18n.t('activerecord.errors.models.site.attributes.design_id.missing_gem', theme: name) rescue Jekyll::Errors::InvalidThemeName => e ExceptionNotifier.notify_exception(e, data: { theme: name, site: File.basename(site.source) }) raise