mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-22 21:26:21 +00:00
guardar cambios en post
This commit is contained in:
parent
efe401d740
commit
ad1ae26e4d
3 changed files with 73 additions and 170 deletions
|
@ -6,6 +6,8 @@ require 'jekyll/utils'
|
|||
# para modificarlos y crear nuevos.
|
||||
#
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
# rubocop:disable Style/MethodMissingSuper
|
||||
# rubocop:disable Style/MissingRespondToMissing
|
||||
class Post < OpenStruct
|
||||
# Atributos por defecto
|
||||
# XXX: Volver document opcional cuando estemos creando
|
||||
|
@ -31,8 +33,11 @@ class Post < OpenStruct
|
|||
layout.metadata.keys.map(&:to_sym)
|
||||
|
||||
# El contenido
|
||||
# TODO: Mover a su propia clase para poder hacer limpiezas
|
||||
# independientemente
|
||||
self.content = document.content
|
||||
self.date = document.date
|
||||
# TODO: idem content
|
||||
self.slug = document.data['slug']
|
||||
|
||||
# Genera un atributo por cada uno de los campos de la plantilla,
|
||||
|
@ -70,8 +75,6 @@ class Post < OpenStruct
|
|||
#
|
||||
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
|
||||
# usando...
|
||||
#
|
||||
# rubocop:disable Style/MethodMissingSuper
|
||||
def method_missing(mid, *args)
|
||||
unless attribute? mid
|
||||
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
||||
|
@ -80,7 +83,6 @@ class Post < OpenStruct
|
|||
|
||||
super(mid, *args)
|
||||
end
|
||||
# rubocop:enable Style/MethodMissingSuper
|
||||
|
||||
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
||||
# plantilla
|
||||
|
@ -93,6 +95,7 @@ class Post < OpenStruct
|
|||
end
|
||||
|
||||
# Genera el post con metadatos en YAML
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def full_content
|
||||
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
|
||||
template = send(metadata)
|
||||
|
@ -100,6 +103,9 @@ class Post < OpenStruct
|
|||
{ metadata.to_s => template.value } unless template.empty?
|
||||
end.compact.inject(:merge)
|
||||
|
||||
# Asegurarse que haya un layout
|
||||
yaml['layout'] = layout.name.to_s
|
||||
|
||||
"#{yaml.to_yaml}---\n\n#{content}"
|
||||
end
|
||||
|
||||
|
@ -117,23 +123,16 @@ class Post < OpenStruct
|
|||
!File.exist?(path) && !site.posts(lang: lang).include?(self)
|
||||
end
|
||||
alias destroy! destroy
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# Guarda los cambios.
|
||||
#
|
||||
# Recién cuando vamos a guardar creamos el Post, porque ya tenemos
|
||||
# todos los datos para escribir el archivo, que es la condición
|
||||
# necesaria para poder crearlo :P
|
||||
# Guarda los cambios
|
||||
def save
|
||||
cleanup!
|
||||
|
||||
return false unless valid?
|
||||
|
||||
return unless write
|
||||
return unless detect_file_rename!
|
||||
return false unless write
|
||||
|
||||
# Vuelve a leer el post para tomar los cambios
|
||||
document.read
|
||||
# add_post_to_site!
|
||||
|
||||
true
|
||||
end
|
||||
alias save! save
|
||||
|
@ -162,16 +161,6 @@ class Post < OpenStruct
|
|||
end
|
||||
alias validate! validate
|
||||
|
||||
# Permite ordenar los posts
|
||||
def <=>(other)
|
||||
@post <=> other.post
|
||||
end
|
||||
|
||||
# ****************
|
||||
# A PARTIR DE ACA ESTAMOS CONSIDERANDO CUALES METODOS QUEDAN Y CUALES
|
||||
# NO
|
||||
# ****************
|
||||
|
||||
def basename_changed?
|
||||
@post.try(:basename) != basename_from_front_matter
|
||||
end
|
||||
|
@ -180,17 +169,6 @@ class Post < OpenStruct
|
|||
new? || @post.data.dig('slug') != slug
|
||||
end
|
||||
|
||||
# Detecta si un valor es un archivo
|
||||
def url?(name)
|
||||
path = get_front_matter(name)
|
||||
return false unless path.is_a?(String) || path.is_a?(Array)
|
||||
|
||||
# El primer valor es '' porque la URL empieza con /
|
||||
[path].flatten.map do |p|
|
||||
p.split('/').second == 'public'
|
||||
end.all?
|
||||
end
|
||||
|
||||
# devuelve las plantillas como strong params, primero los valores
|
||||
# simples, luego los arrays y al final los hashes
|
||||
def template_params
|
||||
|
@ -199,34 +177,18 @@ class Post < OpenStruct
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Completa el front_matter a partir de las variables de otro post que
|
||||
# le sirve de plantilla
|
||||
def front_matter_from_template
|
||||
# XXX: Llamamos a @template en lugar de template porque sino
|
||||
# entramos en una race condition
|
||||
return {} unless @template
|
||||
|
||||
ft = template_fields.map(&:to_front_matter).reduce({}, :merge)
|
||||
# Convertimos el slug en layout
|
||||
ft['layout'] = template.slug
|
||||
|
||||
ft
|
||||
end
|
||||
|
||||
# Solo agregar el post al sitio una vez que lo guardamos
|
||||
#
|
||||
# TODO no sería la forma correcta de hacerlo en Rails
|
||||
# def add_post_to_site!
|
||||
# @site.jekyll.collections[@collection].docs << @post
|
||||
# @site.jekyll.collections[@collection].docs.sort!
|
||||
# def add_post_to_site!
|
||||
# @site.jekyll.collections[@collection].docs << @post
|
||||
# @site.jekyll.collections[@collection].docs.sort!
|
||||
|
||||
# unless @site.collections[@collection].include? self
|
||||
# @site.collections[@collection] << self
|
||||
# @site.collections[@collection].sort!
|
||||
# end
|
||||
# end
|
||||
# unless @site.collections[@collection].include? self
|
||||
# @site.collections[@collection] << self
|
||||
# @site.collections[@collection].sort!
|
||||
# end
|
||||
# end
|
||||
|
||||
# Cambiar el nombre del archivo si cambió el título o la fecha.
|
||||
# Como Jekyll no tiene métodos para modificar un Document, lo
|
||||
|
@ -238,60 +200,21 @@ class Post < OpenStruct
|
|||
|
||||
Rails.logger.info I18n.t('posts.logger.rm', path: path)
|
||||
FileUtils.rm @post.path
|
||||
# replace_post!
|
||||
# replace_post!
|
||||
end
|
||||
|
||||
# Reemplaza el post en el sitio por uno nuevo
|
||||
def replace_post!
|
||||
@old_post = @site.jekyll.collections[@lang].docs.delete @post
|
||||
# TODO: refactorizar
|
||||
# def replace_post!
|
||||
# @old_post = @site.jekyll.collections[@lang].docs.delete @post
|
||||
|
||||
new_post
|
||||
end
|
||||
|
||||
# Obtiene el nombre del archivo a partir de los datos que le
|
||||
# pasemos
|
||||
def basename_from_front_matter
|
||||
ext = get_front_matter('ext') || '.markdown'
|
||||
|
||||
"#{date_as_string}-#{slug}#{ext}"
|
||||
end
|
||||
|
||||
# Toma los datos del front matter local y los mueve a los datos
|
||||
# que van a ir al post. Si hay símbolos se convierten a cadenas,
|
||||
# porque Jekyll trabaja con cadenas. Se excluyen otros datos que no
|
||||
# van en el frontmatter
|
||||
def merge_with_front_matter!(params)
|
||||
@front_matter.merge! Hash[params.to_hash.map do |k, v|
|
||||
[k, v] unless REJECT_FROM_DATA.include? k
|
||||
end.compact]
|
||||
end
|
||||
|
||||
# Carga una copia de los datos del post original excluyendo datos
|
||||
# que no nos interesan
|
||||
def load_front_matter!
|
||||
@front_matter = @post.data.reject do |key, _|
|
||||
REJECT_FROM_DATA.include? key
|
||||
end
|
||||
end
|
||||
# new_post
|
||||
# end
|
||||
|
||||
def cleanup!
|
||||
default_date_is_today!
|
||||
clean_content!
|
||||
slugify_title!
|
||||
update_translations!
|
||||
put_in_order!
|
||||
create_glossary!
|
||||
end
|
||||
|
||||
# Busca las traducciones y actualiza el frontmatter si es necesario
|
||||
def update_translations!
|
||||
return unless translated?
|
||||
return unless slug_changed?
|
||||
|
||||
find_translations.each do |post|
|
||||
post.update_attributes(lang: get_front_matter('lang'))
|
||||
post.save
|
||||
end
|
||||
end
|
||||
|
||||
# Aplica limpiezas básicas del contenido
|
||||
|
@ -301,87 +224,43 @@ class Post < OpenStruct
|
|||
|
||||
# Guarda los cambios en el archivo destino
|
||||
def write
|
||||
r = File.open(path, File::RDWR | File::CREAT, 0o640) do |f|
|
||||
# Bloquear el archivo para que no sea accedido por otro
|
||||
# proceso u otra editora
|
||||
f.flock(File::LOCK_EX)
|
||||
return true if persisted?
|
||||
|
||||
# Empezar por el principio
|
||||
f.rewind
|
||||
|
||||
# Escribir
|
||||
f.write(full_content)
|
||||
|
||||
# Eliminar el resto
|
||||
f.flush
|
||||
f.truncate(f.pos)
|
||||
end
|
||||
|
||||
return true if r.zero?
|
||||
|
||||
add_error file: I18n.t('posts.errors.file')
|
||||
false
|
||||
Site::Writer.new(site: site, file: path,
|
||||
content: full_content, usuarie: usuarie,
|
||||
message: title.value).save
|
||||
end
|
||||
|
||||
def add_error(hash)
|
||||
hash.each_pair do |k, i|
|
||||
@errors[k] = if @errors.key?(k)
|
||||
[@errors[k], i]
|
||||
else
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
@errors
|
||||
# Devuelve le autore de un artículo
|
||||
# XXX: Si se cambia le autore en el editor también cambia quién hace
|
||||
# un cambio y se le puede asignar a cualquier otre.
|
||||
# TODO: Mover la escritura a un servicio que combine escritura, commit
|
||||
# y current_usuarie
|
||||
def usuarie
|
||||
OpenStruct.new(email: author.value.first,
|
||||
name: author.value.first.split('@', 2).first)
|
||||
end
|
||||
|
||||
# Verifica si hace falta escribir cambios
|
||||
def persisted?
|
||||
File.exist?(path) && full_content == File.read(path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_date_is_today!
|
||||
date ||= Time.now
|
||||
self.date ||= Time.now
|
||||
end
|
||||
|
||||
def slugify_title!
|
||||
self.slug = Jekyll::Utils.slugify(title) if slug.blank?
|
||||
end
|
||||
|
||||
# Agregar al final de la cola si no especificamos un orden
|
||||
#
|
||||
# TODO si el artículo tiene una fecha que lo coloca en medio de
|
||||
# la colección en lugar de al final, deberíamos reordenar?
|
||||
def put_in_order!
|
||||
return unless order.nil?
|
||||
|
||||
@front_matter['order'] = @site.posts_for(@collection).count
|
||||
end
|
||||
|
||||
# Crea el artículo de glosario para cada categoría o tag
|
||||
def create_glossary!
|
||||
return unless site.glossary?
|
||||
|
||||
%i[tags categories].each do |i|
|
||||
send(i).each do |c|
|
||||
# TODO: no hardcodear, hacer _configurable
|
||||
next if c == 'Glossary'
|
||||
next if site.posts.find do |p|
|
||||
p.title == c
|
||||
end
|
||||
|
||||
glossary = Post.new(site: site, lang: lang)
|
||||
glossary.update_attributes(
|
||||
title: c,
|
||||
layout: 'glossary',
|
||||
categories: 'Glossary'
|
||||
)
|
||||
|
||||
glossary.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Obtiene el nombre del atributo sin
|
||||
def attribute_name(attr)
|
||||
attr.to_s.split('=').first.to_sym
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
# rubocop:enable Style/MethodMissingSuper
|
||||
# rubocop:enable Style/MissingRespondToMissing
|
||||
|
|
|
@ -94,3 +94,10 @@ Al instanciar un `Post`, se pasan el sitio y la plantilla por defecto.
|
|||
## TODO
|
||||
|
||||
* Leer artículos a medida que se los necesita en lugar de todos juntos.
|
||||
* Reimplementar glosario (se crea un artículo por cada categoría
|
||||
utilizada)
|
||||
* Reimplementar orden de artículos (ver doc)
|
||||
* Detectar cambio de nombre (fecha y slug)
|
||||
* Crear artículos nuevos
|
||||
* Convertir layout a params
|
||||
* Guardar cambios
|
||||
|
|
|
@ -85,5 +85,22 @@ class PostTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test 'se pueden guardar los cambios' do
|
||||
title = SecureRandom.hex
|
||||
@post.title.value = title
|
||||
@post.categories.value << title
|
||||
|
||||
assert @post.save
|
||||
|
||||
Dir.chdir(@site.path) do
|
||||
collection = Jekyll::Collection.new(@site.jekyll, I18n.locale.to_s)
|
||||
document = Jekyll::Document.new(@post.path,
|
||||
site: @site.jekyll,
|
||||
collection: collection)
|
||||
document.read
|
||||
|
||||
assert document.data['categories'].include?(title)
|
||||
assert_equal title, document.data['title']
|
||||
assert_equal 'post', document.data['layout']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue