mirror of
https://0xacab.org/sutty/sutty
synced 2025-03-14 19:58:20 +00:00
Merge branch 'issue-15068' into production.panel.sutty.nl
This commit is contained in:
commit
3a628028a1
21 changed files with 109 additions and 75 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
BRACKETS = /[\[\]]/
|
BRACKETS = /[\[\]]/.freeze
|
||||||
|
|
||||||
# Devuelve el atributo name de un campo anidado en el formato que
|
# Devuelve el atributo name de un campo anidado en el formato que
|
||||||
# esperan los helpers *_field
|
# esperan los helpers *_field
|
||||||
|
|
|
@ -19,8 +19,12 @@ class MetadataArray < MetadataTemplate
|
||||||
true && !private?
|
true && !private?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def titleize?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
value.join(', ')
|
value.select(&:present?).join(', ')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene el valor desde el documento, convirtiéndolo a Array si no lo
|
# Obtiene el valor desde el documento, convirtiéndolo a Array si no lo
|
||||||
|
|
|
@ -13,6 +13,10 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
belongs_to.try(:title).try(:value).to_s
|
||||||
|
end
|
||||||
|
|
||||||
# Obtiene el valor desde el documento.
|
# Obtiene el valor desde el documento.
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
@ -107,6 +111,6 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize(uuid)
|
def sanitize(uuid)
|
||||||
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
uuid.to_s.gsub(/[^a-f0-9-]/i, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,10 @@ class MetadataPredefinedValue < MetadataString
|
||||||
@values ||= layout.dig(:metadata, name, 'values', I18n.locale.to_s)&.invert || {}
|
@values ||= layout.dig(:metadata, name, 'values', I18n.locale.to_s)&.invert || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
values.invert[value].to_s
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Solo permite almacenar los valores predefinidos.
|
# Solo permite almacenar los valores predefinidos.
|
||||||
|
|
|
@ -22,6 +22,10 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def titleize?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def indexable_values
|
def indexable_values
|
||||||
posts.where(uuid: value).map(&:title).map(&:value)
|
posts.where(uuid: value).map(&:title).map(&:value)
|
||||||
end
|
end
|
||||||
|
@ -41,12 +45,12 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
end
|
end
|
||||||
|
|
||||||
def title(post)
|
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
|
end
|
||||||
|
|
||||||
def sanitize(uuid)
|
def sanitize(uuid)
|
||||||
super(uuid.map do |u|
|
super(uuid.map do |u|
|
||||||
u.to_s.gsub(/[^a-f0-9\-]/i, '')
|
u.to_s.gsub(/[^a-f0-9-]/i, '')
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ require 'jekyll/utils'
|
||||||
class MetadataSlug < MetadataTemplate
|
class MetadataSlug < MetadataTemplate
|
||||||
# Trae el slug desde el título si existe o una string al azar
|
# Trae el slug desde el título si existe o una string al azar
|
||||||
def default_value
|
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
|
end
|
||||||
|
|
||||||
def value
|
def value
|
||||||
|
|
|
@ -11,6 +11,10 @@ class MetadataString < MetadataTemplate
|
||||||
true && !private?
|
true && !private?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def titleize?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# No se permite HTML en las strings
|
# No se permite HTML en las strings
|
||||||
|
|
|
@ -16,6 +16,11 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# El valor puede ser parte de un título auto-generado
|
||||||
|
def titleize?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>"
|
"#<#{self.class} site=#{site.name.inspect} post=#{post.id.inspect} value=#{value.inspect}>"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Un campo de texto largo
|
# Un campo de texto largo
|
||||||
class MetadataText < MetadataString; end
|
class MetadataText < MetadataString
|
||||||
|
def titleize?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
18
app/models/metadata_title.rb
Normal file
18
app/models/metadata_title.rb
Normal file
|
@ -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
|
|
@ -12,9 +12,21 @@ class Post
|
||||||
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
||||||
# Otros atributos que no vienen en los metadatos
|
# Otros atributos que no vienen en los metadatos
|
||||||
PRIVATE_ATTRIBUTES = %i[path slug attributes errors].freeze
|
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
|
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 PostError < StandardError; end
|
||||||
class UnknownAttributeError < PostError; end
|
class UnknownAttributeError < PostError; end
|
||||||
|
|
||||||
|
@ -53,10 +65,12 @@ class Post
|
||||||
@layout = args[:layout]
|
@layout = args[:layout]
|
||||||
@site = args[:site]
|
@site = args[:site]
|
||||||
@document = args[:document]
|
@document = args[:document]
|
||||||
@attributes = layout.attributes + PUBLIC_ATTRIBUTES
|
@attributes = (layout.attributes + PUBLIC_ATTRIBUTES).uniq
|
||||||
@errors = {}
|
@errors = {}
|
||||||
@metadata = {}
|
@metadata = {}
|
||||||
|
|
||||||
|
layout.metadata = ATTRIBUTE_DEFINITIONS.merge(layout.metadata).with_indifferent_access
|
||||||
|
|
||||||
# Leer el documento si existe
|
# Leer el documento si existe
|
||||||
# @todo Asignar todos los valores a self[:value] luego de leer
|
# @todo Asignar todos los valores a self[:value] luego de leer
|
||||||
document&.read! unless new?
|
document&.read! unless new?
|
||||||
|
@ -131,6 +145,7 @@ class Post
|
||||||
src = element.attributes['src']
|
src = element.attributes['src']
|
||||||
|
|
||||||
next unless src&.value&.start_with? 'public/'
|
next unless src&.value&.start_with? 'public/'
|
||||||
|
|
||||||
file = MetadataFile.new(site: site, post: self, document: document, layout: layout)
|
file = MetadataFile.new(site: site, post: self, document: document, layout: layout)
|
||||||
file.value['path'] = src.value
|
file.value['path'] = src.value
|
||||||
|
|
||||||
|
@ -192,13 +207,13 @@ class Post
|
||||||
def method_missing(name, *_args)
|
def method_missing(name, *_args)
|
||||||
# Limpiar el nombre del atributo, para que todos los ayudantes
|
# Limpiar el nombre del atributo, para que todos los ayudantes
|
||||||
# reciban el método en limpio
|
# reciban el método en limpio
|
||||||
unless attribute? name
|
raise NoMethodError, I18n.t('exceptions.post.no_method', method: name) unless attribute? name
|
||||||
raise NoMethodError, I18n.t('exceptions.post.no_method', method: name)
|
|
||||||
end
|
|
||||||
|
|
||||||
define_singleton_method(name) do
|
define_singleton_method(name) do
|
||||||
template = layout.metadata[name.to_s]
|
template = layout.metadata[name.to_s]
|
||||||
|
|
||||||
|
return public_send(template['alias'].to_sym) if template.key?('alias')
|
||||||
|
|
||||||
@metadata[name] ||=
|
@metadata[name] ||=
|
||||||
MetadataFactory.build(document: document,
|
MetadataFactory.build(document: document,
|
||||||
post: self,
|
post: self,
|
||||||
|
@ -214,43 +229,6 @@ class Post
|
||||||
public_send name
|
public_send name
|
||||||
end
|
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.
|
# Devuelve los strong params para el layout.
|
||||||
#
|
#
|
||||||
# XXX: Nos gustaría no tener que instanciar Metadata acá, pero depende
|
# XXX: Nos gustaría no tener que instanciar Metadata acá, pero depende
|
||||||
|
@ -427,7 +405,8 @@ class Post
|
||||||
def attribute?(mid)
|
def attribute?(mid)
|
||||||
included = DEFAULT_ATTRIBUTES.include?(mid) ||
|
included = DEFAULT_ATTRIBUTES.include?(mid) ||
|
||||||
PRIVATE_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)
|
included = attributes.include? mid if !included && singleton_class.method_defined?(:attributes)
|
||||||
|
|
||||||
|
|
|
@ -185,24 +185,24 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
# Si les usuaries modifican o crean una licencia, considerarla
|
# Si les usuaries modifican o crean una licencia, considerarla
|
||||||
# personalizada en el panel.
|
# personalizada en el panel.
|
||||||
def update_site_license!
|
def update_site_license!
|
||||||
if site.usuarie?(usuarie) && post.layout.name == :license && !site.licencia.custom?
|
return unless site.usuarie?(usuarie) && post.layout.name == :license && !site.licencia.custom?
|
||||||
site.update licencia: Licencia.find_by_icons('custom')
|
|
||||||
end
|
site.update licencia: Licencia.find_by_icons('custom')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Encuentra todos los posts anidados y los crea o modifica
|
# Encuentra todos los posts anidados y los crea o modifica
|
||||||
def create_nested_posts!(post, params)
|
def create_nested_posts!(post, params)
|
||||||
post.nested_attributes.each do |nested_attribute|
|
post.nested_attributes.each do |nested_attribute|
|
||||||
nested_metadata = post[nested_attribute]
|
nested_metadata = post[nested_attribute]
|
||||||
# @todo find_or_initialize
|
# @todo find_or_initialize
|
||||||
nested_post = nested_metadata.has_one || site.posts(lang: post.lang.value).build(layout: nested_metadata.nested)
|
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_params = params.require(nested_attribute).permit(nested_post.params).to_hash
|
||||||
|
|
||||||
# Completa la relación 1:1
|
# Completa la relación 1:1
|
||||||
nested_params[nested_metadata.inverse.to_s] = post.uuid.value
|
nested_params[nested_metadata.inverse.to_s] = post.uuid.value
|
||||||
post[nested_attribute].value = nested_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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
= render 'layouts/breadcrumb'
|
= render 'layouts/breadcrumb'
|
||||||
= render 'layouts/flash'
|
= render 'layouts/flash'
|
||||||
|
|
||||||
= yield(:post_form)
|
|
||||||
= yield
|
= yield
|
||||||
|
|
||||||
- if flash[:js]
|
- if flash[:js]
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
- cache [metadata, I18n.locale] do
|
- cache [metadata, I18n.locale] do
|
||||||
= render("posts/attributes/#{type}",
|
= render("posts/attributes/#{type}",
|
||||||
base: base, post: post, attribute: attribute,
|
base: base, post: post, attribute: attribute,
|
||||||
metadata: metadata, site: site,
|
metadata: metadata, site: site,
|
||||||
dir: dir, locale: locale,
|
dir: dir, locale: locale,
|
||||||
autofocus: (post.attributes.first == attribute))
|
autofocus: (post.attributes.first == attribute))
|
||||||
|
|
|
@ -9,10 +9,11 @@
|
||||||
- next if attribute == :date
|
- next if attribute == :date
|
||||||
- next if attribute == :draft
|
- next if attribute == :draft
|
||||||
- next if attribute == inverse
|
- next if attribute == inverse
|
||||||
|
|
||||||
- metadata = post[attribute]
|
- metadata = post[attribute]
|
||||||
|
|
||||||
- cache [post, metadata, I18n.locale] do
|
- cache [post, metadata, I18n.locale] do
|
||||||
= render "posts/attributes/#{metadata.type}",
|
= render "posts/attributes/#{metadata.type}",
|
||||||
base: base, post: post, attribute: attribute,
|
base: base, post: post, attribute: attribute,
|
||||||
metadata: metadata, site: site,
|
metadata: metadata, site: site,
|
||||||
dir: dir, locale: locale, autofocus: false
|
dir: dir, locale: locale, autofocus: false
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
- dir = t("locales.#{@locale}.dir")
|
- dir = t("locales.#{@locale}.dir")
|
||||||
|
|
||||||
-# Comienza el formulario
|
-# 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
|
-# Botones de guardado
|
||||||
= render 'posts/submit', site: site, post: post
|
= render 'posts/submit', site: site, post: post
|
||||||
|
|
||||||
|
@ -45,3 +44,6 @@
|
||||||
|
|
||||||
-# Botones de guardado
|
-# Botones de guardado
|
||||||
= render 'posts/submit', site: site, post: post
|
= render 'posts/submit', site: site, post: post
|
||||||
|
|
||||||
|
-# Formularios usados por los modales
|
||||||
|
= yield(:post_form)
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
end
|
end
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
id: base,
|
id: base,
|
||||||
multipart: true,
|
multipart: true,
|
||||||
class: 'form post ',
|
class: 'form post ',
|
||||||
'hx-swap': params.require(:swap),
|
'hx-swap': params.require(:swap),
|
||||||
|
@ -40,3 +40,5 @@
|
||||||
|
|
||||||
-# Dibuja cada atributo
|
-# Dibuja cada atributo
|
||||||
= render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale, except: except
|
= render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale, except: except
|
||||||
|
|
||||||
|
= yield(:post_form)
|
||||||
|
|
3
app/views/posts/attribute_ro/_title.haml
Normal file
3
app/views/posts/attribute_ro/_title.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td{ dir: dir, lang: locale }= metadata.value
|
0
app/views/posts/attributes/_title.haml
Normal file
0
app/views/posts/attributes/_title.haml
Normal file
|
@ -4,7 +4,7 @@
|
||||||
.col-12.col-lg-8
|
.col-12.col-lg-8
|
||||||
%article.content.table-responsive-md
|
%article.content.table-responsive-md
|
||||||
= link_to t('posts.edit_post'),
|
= link_to t('posts.edit_post'),
|
||||||
edit_site_post_path(@site, @post.id),
|
edit_site_post_path(@site, @post.id),
|
||||||
class: 'btn btn-secondary btn-block'
|
class: 'btn btn-secondary btn-block'
|
||||||
|
|
||||||
= render 'table', dir: dir, site: @site, locale: @locale, post: @post, title: t('.front_matter')
|
= render 'table', dir: dir, site: @site, locale: @locale, post: @post, title: t('.front_matter')
|
||||||
|
|
|
@ -126,7 +126,8 @@ module Jekyll
|
||||||
|
|
||||||
unless spec
|
unless spec
|
||||||
I18n.with_locale(locale) do
|
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
|
rescue Jekyll::Errors::InvalidThemeName => e
|
||||||
ExceptionNotifier.notify_exception(e, data: { theme: name, site: File.basename(site.source) })
|
ExceptionNotifier.notify_exception(e, data: { theme: name, site: File.basename(site.source) })
|
||||||
raise
|
raise
|
||||||
|
|
Loading…
Reference in a new issue