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

View file

@ -31,8 +31,7 @@ class PostsController < ApplicationController
def new def new
authorize Post authorize Post
@site = find_site @site = find_site
# TODO: Implementar layout @post = @site.posts.build(lang: I18n.locale, layout: params[:layout])
@post = @site.posts.build(lang: I18n.locale)
end end
def create def create
@ -90,4 +89,17 @@ class PostsController < ApplicationController
service.destroy service.destroy
redirect_to site_posts_path(@site) redirect_to site_posts_path(@site)
end 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 end

View file

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

View file

@ -57,7 +57,7 @@ class PostRelation < Array
def build_layout(layout = nil) def build_layout(layout = nil)
return layout if layout.is_a? Layout return layout if layout.is_a? Layout
site.layouts[layout || :post] site.layouts[layout.try(:to_sym) || :post]
end end
# Devuelve una colección Jekyll que hace pasar el documento # 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 @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 # No fallar si no existe colección para este idioma
# XXX: queremos fallar silenciosamente? # XXX: queremos fallar silenciosamente?
(collections[lang.to_s].try(:docs) || []).each do |doc| (docs || []).each do |doc|
layout = layouts[doc.data['layout'].to_sym] layout = layouts[doc.data['layout'].to_sym]
@posts[lang].build(document: doc, layout: layout, lang: lang) @posts[lang].build(document: doc, layout: layout, lang: lang)
@ -202,8 +206,7 @@ class Site < ApplicationRecord
# @return Array # @return Array
def everything_of(attr, lang: nil) def everything_of(attr, lang: nil)
posts(lang: lang).map do |p| posts(lang: lang).map do |p|
# XXX: Tener cuidado con los métodos que no existan p.send(attr).try(:value) if p.attribute? attr
p.send(attr).try :value
end.flatten.uniq.compact end.flatten.uniq.compact
end end
@ -217,47 +220,6 @@ class Site < ApplicationRecord
status == 'enqueued' status == 'enqueued'
end 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 # Obtener una ruta disponible para Sutty
# #
# TODO: Refactorizar y testear # TODO: Refactorizar y testear

View file

@ -85,9 +85,13 @@ class Site
!commits.empty? !commits.empty?
end end
# Guarda los cambios en git, de a un archivo por vez # Guarda los cambios en git
def commit(file:, usuarie:, message:, remove: false) 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 # Escribir los cambios para que el repositorio se vea tal cual
rugged.index.write rugged.index.write

View file

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

View file

@ -39,19 +39,50 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
post post
end 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 private
def commit(action:) def commit(action:, file: nil)
site.repository.commit(file: post.path.absolute, site.repository.commit(file: file || post.path.absolute,
usuarie: usuarie, usuarie: usuarie,
remove: action == :destroyed, remove: action == :destroyed,
message: I18n.t("post_service.#{action}", message: I18n.t("post_service.#{action}",
title: post.title.value)) title: post.try(:title).try(:value)))
end end
# Solo permitir cambiar estos atributos de cada articulo # Solo permitir cambiar estos atributos de cada articulo
def post_params def post_params
params.require(:post).permit(post.params) params.require(:post).permit(post.params)
end end
def lang
params[:post][:lang] || I18n.locale
end
end end
# rubocop:enable Metrics/BlockLength # rubocop:enable Metrics/BlockLength

View file

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

View file

@ -19,6 +19,7 @@ en:
created: 'Created "%{title}"' created: 'Created "%{title}"'
updated: 'Updated "%{title}"' updated: 'Updated "%{title}"'
destroyed: 'Removed "%{title}"' destroyed: 'Removed "%{title}"'
reorder: 'Reorder'
metadata: metadata:
array: array:
cant_be_empty: 'This field cannot be empty' cant_be_empty: 'This field cannot be empty'
@ -296,7 +297,7 @@ en:
url: 'Demo' url: 'Demo'
licencia: 'Read the license' licencia: 'Read the license'
licencia: licencia:
title: 'License for the site and everything in it' title: 'License for the site and everything published on it'
url: 'Read the license' url: 'Read the license'
privacidad: privacidad:
title: 'Privacy policy and code of conduct' 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.' invalid_help: 'Some fields need attention! Please search for the fields marked as invalid.'
sending_help: Saving, please wait... sending_help: Saving, please wait...
attributes: attributes:
lang:
label: Language
date: date:
label: 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. 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}"' created: 'Creado "%{title}"'
updated: 'Modificado "%{title}"' updated: 'Modificado "%{title}"'
destroyed: 'Eliminado "%{title}"' destroyed: 'Eliminado "%{title}"'
reorder: 'Reordenados'
metadata: metadata:
array: array:
cant_be_empty: 'El campo no puede estar vacío' cant_be_empty: 'El campo no puede estar vacío'
@ -306,7 +307,7 @@ es:
url: 'Demostración' url: 'Demostración'
license: 'Leer la licencia' license: 'Leer la licencia'
licencia: licencia:
title: 'Licencia del sitio y todo lo que publiques' title: 'Licencia del sitio y todo lo publicado'
url: 'Leer la licencia' url: 'Leer la licencia'
privacidad: privacidad:
title: Políticas de privacidad y código de convivencia 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' invalid_help: '¡Te faltan completar algunos campos! Busca los que estén marcados como inválidos'
sending_help: Guardando, por favor espera... sending_help: Guardando, por favor espera...
attributes: attributes:
lang:
label: Idioma
date: date:
label: Fecha 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. 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' post 'collaborate', to: 'collaborations#accept_collaboration'
# Gestionar artículos # Gestionar artículos
post :'posts/reorder', to: 'posts#reorder'
resources :posts resources :posts
# Gestionar traducciones # Gestionar traducciones

View file

@ -1,134 +1,15 @@
# Reordenar los articulos # Reordenar los articulos
La interfaz reordena los articulos y los envia en ese orden particular Todos los posts tienen un campo `order`.
(se puede enviar un numero de orden completado con js para estar mas
segurxs). Entonces el algoritmo...
* Chequea que los posts tengan fechas en orden El orden se actualiza en base al orden cronológico.
* 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.
En la plantilla se puede ordenar cronólogicamente o por orden numérico. En la plantilla se puede ordenar cronólogicamente o por orden numérico.
Es más simple de implementar y no tiene comportamientos inesperados, El orden es independiente de otros metadatos (como layout, categoria,
como "por qué cambió de fecha?" 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 @rol = create :rol
@site = @rol.site @site = @rol.site
@usuarie = @rol.usuarie @usuarie = @rol.usuarie
@post = @site.posts.build(title: SecureRandom.hex) @post = @site.posts.build(title: SecureRandom.hex,
description: SecureRandom.hex)
@post.save @post.save
@authorization = { @authorization = {
@ -33,6 +34,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
params: { params: {
post: { post: {
title: title, title: title,
description: title,
date: 2.days.ago.strftime('%F') date: 2.days.ago.strftime('%F')
} }
} }
@ -62,8 +64,14 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
test 'se pueden actualizar' do test 'se pueden actualizar' do
title = SecureRandom.hex title = SecureRandom.hex
patch site_post_url(@site, @post.id), headers: @authorization, patch site_post_url(@site, @post.id),
params: { post: { title: title } } headers: @authorization,
params: {
post: {
title: title,
description: title
}
}
assert_equal 302, response.status assert_equal 302, response.status
@ -133,4 +141,21 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
assert_equal 200, response.status assert_equal 200, response.status
assert_match I18n.t('metadata.image.not_an_image'), response.body assert_match I18n.t('metadata.image.not_an_image'), response.body
end 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 end

View file

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

View file

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