5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-17 12:36:22 +00:00

Merge remote-tracking branch 'origin/rails' into prosemirror

This commit is contained in:
void 2019-11-07 14:17:40 -03:00
commit b064a50ade
17 changed files with 137 additions and 209 deletions

View file

@ -115,7 +115,7 @@ GEM
commonmarker (0.20.1)
ruby-enum (~> 0.5)
concurrent-ruby (1.1.5)
crass (1.0.4)
crass (1.0.5)
database_cleaner (1.7.0)
devise (4.7.1)
bcrypt (~> 3.0)
@ -220,7 +220,7 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loofah (2.2.3)
loofah (2.3.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -234,7 +234,7 @@ GEM
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.11.3)
mobility (0.8.8)
mobility (0.8.9)
i18n (>= 0.6.10, < 2)
request_store (~> 1.0)
net-scp (2.0.0)
@ -242,7 +242,7 @@ GEM
net-ssh (5.2.0)
netaddr (2.0.3)
nio4r (2.5.1)
nokogiri (1.10.4)
nokogiri (1.10.5)
mini_portile2 (~> 2.4.0)
orm_adapter (0.5.0)
parallel (1.17.0)
@ -282,8 +282,8 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.2.0)
loofah (~> 2.2, >= 2.2.2)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)

View file

@ -31,8 +31,7 @@ class PostsController < ApplicationController
def new
authorize Post
@site = find_site
# TODO: Implementar layout
@post = @site.posts.build(lang: I18n.locale)
@post = @site.posts.build(lang: I18n.locale, layout: params[:layout])
end
def create
@ -90,4 +89,17 @@ class PostsController < ApplicationController
service.destroy
redirect_to site_posts_path(@site)
end
# Reordenar los artículos
def reorder
@site = find_site
authorize @site
service = PostService.new(site: @site,
usuarie: current_usuarie,
params: params)
service.reorder
redirect_to site_posts_path(@site)
end
end

View file

@ -24,7 +24,7 @@ class MetadataPath < MetadataTemplate
private
def ext
document.extname.blank? ? '.markdown' : document.extname
document.data['ext'].blank? ? '.markdown' : document.data['ext']
end
def lang

View file

@ -57,7 +57,7 @@ class PostRelation < Array
def build_layout(layout = nil)
return layout if layout.is_a? Layout
site.layouts[layout || :post]
site.layouts[layout.try(:to_sym) || :post]
end
# Devuelve una colección Jekyll que hace pasar el documento

View file

@ -173,9 +173,13 @@ class Site < ApplicationRecord
@posts[lang] = PostRelation.new site: self
# Jekyll lee los documentos en orden cronológico pero los invierte
# durante la renderización. Usamos el orden cronológico inverso por
# defecto para mostrar los artículos más nuevos primero.
docs = collections[lang.to_s].try(:docs).try(:sort) { |a, b| b <=> a }
# No fallar si no existe colección para este idioma
# XXX: queremos fallar silenciosamente?
(collections[lang.to_s].try(:docs) || []).each do |doc|
(docs || []).each do |doc|
layout = layouts[doc.data['layout'].to_sym]
@posts[lang].build(document: doc, layout: layout, lang: lang)
@ -202,8 +206,7 @@ class Site < ApplicationRecord
# @return Array
def everything_of(attr, lang: nil)
posts(lang: lang).map do |p|
# XXX: Tener cuidado con los métodos que no existan
p.send(attr).try :value
p.send(attr).try(:value) if p.attribute? attr
end.flatten.uniq.compact
end
@ -217,47 +220,6 @@ class Site < ApplicationRecord
status == 'enqueued'
end
# Verifica si los posts están ordenados
def ordered?(lang: nil)
posts(lang: lang).map(&:order).all?
end
# Reordena la colección usando la posición informada
#
# new_order es un hash cuya key es la posición actual del post y el
# valor la posición nueva
#
# TODO: Refactorizar y testear
def reorder_collection(collection, new_order)
# Tenemos que pasar el mismo orden
return if new_order.values.map(&:to_i).sort != new_order.keys.map(&:to_i).sort
# Solo traer los posts que vamos a modificar
posts_to_order = posts_for(collection).values_at(*new_order.keys.map(&:to_i))
# Recorre todos los posts y asigna el nuevo orden
posts_to_order.each_with_index do |p, i|
# Usar el index si el artículo no estaba ordenado, para tener una
# ruta de adopción
oo = (p.order || i).to_s
no = new_order[oo].to_i
# No modificar nada si no hace falta
next if p.order == no
p.update_attributes order: no
p.save
end
posts_to_order.map(&:ordered?).all?
end
# Reordena la colección usando la posición actual de los artículos
def reorder_collection!(collection = 'posts')
order = Hash[posts_for(collection).count.times.map { |i| [i.to_s, i.to_s] }]
reorder_collection collection, order
end
alias reorder_posts! reorder_collection!
# Obtener una ruta disponible para Sutty
#
# TODO: Refactorizar y testear

View file

@ -85,9 +85,13 @@ class Site
!commits.empty?
end
# Guarda los cambios en git, de a un archivo por vez
# Guarda los cambios en git
def commit(file:, usuarie:, message:, remove: false)
remove ? rm(file) : add(file)
file = [file] unless file.respond_to? :each
file.each do |f|
remove ? rm(f) : add(f)
end
# Escribir los cambios para que el repositorio se vea tal cual
rugged.index.write

View file

@ -67,6 +67,11 @@ class SitePolicy
pull?
end
# Solo les usuaries pueden reordenar artículos
def reorder?
site.usuarie? usuarie
end
private
def current_role

View file

@ -39,19 +39,50 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
post
end
# Reordena todos los posts que soporten orden de acuerdo a un array
# con las nuevas posiciones. La posición actual la da la posición en
# el array.
#
# [ 1, 0, 2 ] => mover el elemento 2 a la posición 1, mantener 3
def reorder
posts = site.posts(lang: lang)
reorder = params.require(:post).permit(reorder: [])
.try(:[], :reorder)
.try(:map, &:to_i) || []
# Tenemos que pasar un array con la misma cantidad de elementos
return false if reorder.size != posts.size
files = reorder.map.with_index do |new, cur|
post = posts[cur]
next unless post.attributes.include? :order
post.usuaries << usuarie
post.order.value = new
post.path.absolute
end.compact
# TODO: Implementar transacciones!
posts.save_all && commit(action: :reorder, file: files)
end
private
def commit(action:)
site.repository.commit(file: post.path.absolute,
def commit(action:, file: nil)
site.repository.commit(file: file || post.path.absolute,
usuarie: usuarie,
remove: action == :destroyed,
message: I18n.t("post_service.#{action}",
title: post.title.value))
title: post.try(:title).try(:value)))
end
# Solo permitir cambiar estos atributos de cada articulo
def post_params
params.require(:post).permit(post.params)
end
def lang
params[:post][:lang] || I18n.locale
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -74,6 +74,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
post: {
lang: lang,
title: site.licencia.name,
description: I18n.t('sites.form.licencia.title'),
author: %w[Sutty],
permalink: "#{I18n.t('activerecord.models.licencia').downcase}/",
content: CommonMarker.render_html(site.licencia.deed)

View file

@ -19,6 +19,7 @@ en:
created: 'Created "%{title}"'
updated: 'Updated "%{title}"'
destroyed: 'Removed "%{title}"'
reorder: 'Reorder'
metadata:
array:
cant_be_empty: 'This field cannot be empty'
@ -296,7 +297,7 @@ en:
url: 'Demo'
licencia: 'Read the license'
licencia:
title: 'License for the site and everything in it'
title: 'License for the site and everything published on it'
url: 'Read the license'
privacidad:
title: 'Privacy policy and code of conduct'
@ -338,6 +339,8 @@ en:
invalid_help: 'Some fields need attention! Please search for the fields marked as invalid.'
sending_help: Saving, please wait...
attributes:
lang:
label: Language
date:
label: Date
help: Publication date for this post. If you use a date in the future the post won't be published until then.

View file

@ -19,6 +19,7 @@ es:
created: 'Creado "%{title}"'
updated: 'Modificado "%{title}"'
destroyed: 'Eliminado "%{title}"'
reorder: 'Reordenados'
metadata:
array:
cant_be_empty: 'El campo no puede estar vacío'
@ -306,7 +307,7 @@ es:
url: 'Demostración'
license: 'Leer la licencia'
licencia:
title: 'Licencia del sitio y todo lo que publiques'
title: 'Licencia del sitio y todo lo publicado'
url: 'Leer la licencia'
privacidad:
title: Políticas de privacidad y código de convivencia
@ -348,6 +349,8 @@ es:
invalid_help: '¡Te faltan completar algunos campos! Busca los que estén marcados como inválidos'
sending_help: Guardando, por favor espera...
attributes:
lang:
label: Idioma
date:
label: Fecha
help: La fecha de publicación del artículo. Si colocas una fecha en el futuro no se publicará hasta ese día.

View file

@ -45,6 +45,7 @@ Rails.application.routes.draw do
post 'collaborate', to: 'collaborations#accept_collaboration'
# Gestionar artículos
post :'posts/reorder', to: 'posts#reorder'
resources :posts
# Gestionar traducciones

View file

@ -1,134 +1,15 @@
# Reordenar los articulos
La interfaz reordena los articulos y los envia en ese orden particular
(se puede enviar un numero de orden completado con js para estar mas
segurxs). Entonces el algoritmo...
Todos los posts tienen un campo `order`.
* Chequea que los posts tengan fechas en orden
* Si alguno(s) no tienen, busca fechas intermedias
* Cuando todos tienen fechas en orden, guarda los cambios modificando
cada post
---
* Compara el nuevo orden con el viejo para saber las diferencias
---
* Recorre el nuevo orden uno por uno
* Se fija si el anterior tiene una fecha menor
* Se fija si el siguiente tiene una fecha mayor
* Se autoasigna una fecha en el medio
* Se guarda al final
---
* Todos los posts tienen un ID (su id en el array de la colección)
* Toma el post actual
* Mueve el post a una posición arbitraria con un nuevo id
* Toma la fecha del post siguiente (nuevo_id + 1)
* Recorre hacia atrás los anteriores, corriéndolos de a un día hasta una
fecha anterior
* Los posts posteriores no se tocan
Teniendo estos artículos
```
1 2017-01-01
2 2017-01-02
3 2017-01-03
4 2017-01-04
5 2017-01-05
```
Movemos el artículo 2 a la posición 4
```
1 1 2017-01-01
2 3 2017-01-03
3 4 2017-01-04
4 2 2017-01-02
5 5 2017-01-05
```
Reordenamos las fechas
```
1 1 2017-01-01 2017-01-01
2 3 2017-01-03 2017-01-02
3 4 2017-01-04 2017-01-03
4 2 2017-01-02 2017-01-04
5 5 2017-01-05 2017-01-05
```
Movemos varios
```
1 4 2017-01-04
2 5 2017-01-05
3 2 2017-01-02
4 3 2017-01-03
5 1 2017-01-01
```
Cual es la fecha desde la que se empieza? Vamos hacia atras o hacia
adelante?
Hacia atrás
```
1 4 2017-01-04 2016-12-28
2 5 2017-01-05 2016-12-29
3 2 2017-01-02 2016-12-30
4 3 2017-01-03 2016-12-31
5 1 2017-01-01 2017-01-01
```
Hacia adelante
```
1 4 2017-01-04 2017-01-04
2 5 2017-01-05 2017-01-05
3 2 2017-01-02 2017-01-06
4 3 2017-01-03 2017-01-07
5 1 2017-01-01 2017-01-08
```
En si las fechas no importan, porque estamos priorizando el orden, las
fechas son arbitrarias para engañar a jekyll a tener los posts en cierto
orden.
Por el contrario, para mantener los cambios mínimos, podemos reemplazar
hacia adelante comenzando desde la fecha mas baja del orden original.
```
1 4 2017-01-04 2017-01-01
2 5 2017-01-05 2017-01-02
3 2 2017-01-02 2017-01-03
4 3 2017-01-03 2017-01-04
5 1 2017-01-01 2017-01-05
```
No quiere decir que en ordenes de fechas mas dispersas se mantengan los
cambios mínimos.
También podemos tomar todo el set original de fechas y asociarselo al
orden nuevo de posts. Las fechas se mantienen igual, pero cambia el
orden de los posts.
Qué pasa cuando dos o más artículos comparten la misma fecha?
Normalmente se ordenan por nombre, pero en este algoritmo no entra ese
orden, se asume que siempre están una fecha adelante o atrás
---
Todos los posts tienen un campo order.
El orden se actualiza en base al orden cronologico.
El orden se actualiza en base al orden cronológico.
En la plantilla se puede ordenar cronólogicamente o por orden numérico.
Es más simple de implementar y no tiene comportamientos inesperados,
como "por qué cambió de fecha?"
El orden es independiente de otros metadatos (como layout, categoria,
etc), todos los artículos siguen un orden
Como el orden es un metadato, tenemos que ignorar los tipos de posts que
no lo tienen
El orden por defecto es orden natural, más bajo es más alto.

View file

@ -1,11 +0,0 @@
--- lib/mobility/arel/visitor.rb.orig 2019-08-29 14:20:24.180122614 -0300
+++ lib/mobility/arel/visitor.rb 2019-08-29 14:18:38.146723362 -0300
@@ -14,7 +14,7 @@
private
- def visit(object)
+ def visit(object, collection = nil)
super
rescue TypeError
visit_default(object)

View file

@ -7,7 +7,8 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
@rol = create :rol
@site = @rol.site
@usuarie = @rol.usuarie
@post = @site.posts.build(title: SecureRandom.hex)
@post = @site.posts.build(title: SecureRandom.hex,
description: SecureRandom.hex)
@post.save
@authorization = {
@ -33,6 +34,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
params: {
post: {
title: title,
description: title,
date: 2.days.ago.strftime('%F')
}
}
@ -62,8 +64,14 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
test 'se pueden actualizar' do
title = SecureRandom.hex
patch site_post_url(@site, @post.id), headers: @authorization,
params: { post: { title: title } }
patch site_post_url(@site, @post.id),
headers: @authorization,
params: {
post: {
title: title,
description: title
}
}
assert_equal 302, response.status
@ -133,4 +141,21 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
assert_equal 200, response.status
assert_match I18n.t('metadata.image.not_an_image'), response.body
end
test 'se pueden reordenar' do
lang = I18n.available_locales.sample
posts = @site.posts(lang: lang)
reorder = posts.each_index.to_a.shuffle
post site_posts_reorder_url(@site),
headers: @authorization,
params: { post: { lang: lang, reorder: reorder } }
@site = Site.find @site.id
assert_equal I18n.t('post_service.reorder'),
@site.repository.rugged.head.target.message
assert_equal reorder,
@site.posts(lang: lang).map(&:order).map(&:value)
end
end

View file

@ -43,11 +43,12 @@ class EditorTest < ActionDispatch::IntegrationTest
params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex,
content: content
}
}
@post = @site.posts.last
@post = @site.posts.first
assert_equal md_content.strip, @post.content.value
end
@ -61,11 +62,12 @@ class EditorTest < ActionDispatch::IntegrationTest
params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex,
content: trix_orig
}
}
@post = @site.posts.last
@post = @site.posts.first
assert_equal trix_md.strip, @post.content.value
end

View file

@ -5,7 +5,8 @@ require 'test_helper'
class PostTest < ActiveSupport::TestCase
setup do
@site = create :site
@post = @site.posts.create(title: SecureRandom.hex)
@post = @site.posts.create(title: SecureRandom.hex,
description: SecureRandom.hex)
end
teardown do
@ -65,6 +66,7 @@ class PostTest < ActiveSupport::TestCase
test 'se pueden guardar los cambios' do
title = SecureRandom.hex
@post.title.value = title
@post.description.value = title
@post.categories.value << title
assert @post.save
@ -78,6 +80,7 @@ class PostTest < ActiveSupport::TestCase
assert document.data['categories'].include?(title)
assert_equal title, document.data['title']
assert_equal title, document.data['description']
assert_equal 'post', document.data['layout']
end
end
@ -136,14 +139,16 @@ class PostTest < ActiveSupport::TestCase
end
test 'new?' do
post = @site.posts.build title: SecureRandom.hex
post = @site.posts.build title: SecureRandom.hex,
description: SecureRandom.hex
assert post.new?
assert post.save
assert_not post.new?
end
test 'no podemos pisar otros archivos' do
post = @site.posts.create title: SecureRandom.hex
post = @site.posts.create title: SecureRandom.hex,
description: SecureRandom.hex
@post.slug.value = post.slug.value
@post.date.value = post.date.value
@ -157,6 +162,7 @@ class PostTest < ActiveSupport::TestCase
post = @site.posts.build(layout: :post)
post.title.value = SecureRandom.hex
post.content.value = SecureRandom.hex
post.description.value = SecureRandom.hex
assert post.new?
assert post.save
@ -165,9 +171,12 @@ class PostTest < ActiveSupport::TestCase
test 'se pueden inicializar con valores' do
title = SecureRandom.hex
post = @site.posts.build(title: title, content: title)
post = @site.posts.build(title: title,
description: title,
content: title)
assert_equal title, post.title.value
assert_equal title, post.description.value
assert_equal title, post.content.value
assert post.save
end