versión preliminar de los artículos closes #176

This commit is contained in:
f 2020-10-30 18:57:46 -03:00
parent caaafc50c0
commit 74ce28c447
19 changed files with 168 additions and 12 deletions

View file

@ -57,6 +57,10 @@ gem 'jekyll', git: 'https://0xacab.org/sutty/jekyll/jekyll.git',
branch: 'master' branch: 'master'
gem 'jekyll-data', require: 'jekyll-data', gem 'jekyll-data', require: 'jekyll-data',
git: 'https://0xacab.org/sutty/jekyll/jekyll-data.git' git: 'https://0xacab.org/sutty/jekyll/jekyll-data.git'
gem 'jekyll-commonmark'
gem 'jekyll-images'
gem 'jekyll-include-cache'
gem 'sutty-liquid'
gem 'lockbox' gem 'lockbox'
gem 'mini_magick' gem 'mini_magick'
gem 'mobility' gem 'mobility'

View file

@ -236,6 +236,9 @@ GEM
nokogiri (>= 1.6) nokogiri (>= 1.6)
jbuilder (2.10.1) jbuilder (2.10.1)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
jekyll-commonmark (1.3.1)
commonmarker (~> 0.14)
jekyll (>= 3.7, < 5.0)
jekyll-feed (0.15.1) jekyll-feed (0.15.1)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-images (0.2.7) jekyll-images (0.2.7)
@ -590,7 +593,10 @@ DEPENDENCIES
inline_svg inline_svg
jbuilder (~> 2.5) jbuilder (~> 2.5)
jekyll! jekyll!
jekyll-commonmark
jekyll-data! jekyll-data!
jekyll-images
jekyll-include-cache
letter_opener letter_opener
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
lockbox lockbox
@ -625,6 +631,7 @@ DEPENDENCIES
sucker_punch sucker_punch
sutty-donaciones-jekyll-theme sutty-donaciones-jekyll-theme
sutty-jekyll-theme sutty-jekyll-theme
sutty-liquid
sutty-minima sutty-minima
symbol-fstring symbol-fstring
terminal-table terminal-table

View file

@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
around_action :set_locale around_action :set_locale
rescue_from ActionController::RoutingError, with: :page_not_found rescue_from ActionController::RoutingError, with: :page_not_found
rescue_from ActionController::ParameterMissing, with: :page_not_found
before_action do before_action do
Rack::MiniProfiler.authorize_request if current_usuarie&.email&.ends_with?('@' + ENV.fetch('SUTTY', 'sutty.nl')) Rack::MiniProfiler.authorize_request if current_usuarie&.email&.ends_with?('@' + ENV.fetch('SUTTY', 'sutty.nl'))

View file

@ -52,6 +52,18 @@ class PostsController < ApplicationController
fresh_when @post fresh_when @post
end end
# Genera una previsualización del artículo.
#
# TODO: No todos los artículos tienen previsualización!
def preview
@site = find_site
@post = @site.posts(lang: locale).find params[:post_id]
authorize @post
render html: @post.render
end
def new def new
authorize Post authorize Post
@site = find_site @site = find_site

View file

@ -106,6 +106,27 @@ class SitesController < ApplicationController
redirect_to sites_path redirect_to sites_path
end end
# Obtiene y streamea archivos estáticos desde el repositorio mismo,
# pero sólo los públicos (es decir los archivos subidos desde Sutty).
def static_file
authorize site
file = params.require(:file) + '.' + params.require(:format)
raise ActionController::RoutingError unless file.start_with? 'public/'
path = site.relative_path file
raise ActionController::RoutingError unless File.exist? path
# TODO: Hacer esto usa recursos, pero menos que generar el sitio
# cada vez. Para poder usar X-Accel tendríamos que montar los
# repositorios en el servidor web, cosa que no queremos, o hacer
# links simbólicos desde todos los public, o usar un servidor web
# local que soporte sendfile mejor que Rails (nghttpd?)
send_file path
end
private private
def site def site

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Jekyll
module Tags
class Base < Liquid::Tag
def render(context)
context.registers[:site].config['url']
end
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Jekyll
module Tags
# Genera un tag vacío que sirve para reemplazar tags provistos por
# complementos que no vamos a cargar dentro del panel (seo, feed,
# etc.) Lo correcto sería modificar Liquid::Document para que
# ignore los tags desconocidos, pero en nuestras pruebas los toma
# como el comienzo de un bloque e ignora HTML adyacente, así que
# preferimos avanzar con una lista predeterminada.
#
# @see config/initializers/core_extensions.rb
class Empty < Liquid::Tag
def render(_)
''
end
end
end
end

View file

@ -75,6 +75,51 @@ class Post < OpenStruct
"#<Post id=\"#{id}\">" "#<Post id=\"#{id}\">"
end end
# Renderiza el artículo para poder previsualizarlo. Leemos solo la
# información básica, con lo que no van a funcionar artículos
# relacionados y otras cuestiones.
#
# @see app/lib/jekyll/tags/base.rb
def render
Dir.chdir site.path do
# Compatibilidad con jekyll-locales, necesario para el filtro
# date_local
#
# TODO: Cambiar el locale en otro lado
site.jekyll.config['lang'] = lang.value
site.jekyll.config['locale'] = lang.value
# Payload básico con traducciones.
document.renderer.payload = {
'site' => {
'data' => site.data,
'i18n' => site.data[lang.value],
'lang' => lang.value,
'locale' => lang.value
},
'page' => document.to_liquid
}
# Renderizar lo estrictamente necesario y convertir a HTML para
# poder reemplazar valores.
html = Nokogiri::HTML document.renderer.render_document
# Las imágenes se cargan directamente desde el repositorio, porque
# no son públicas hasta que se publica el artículo.
html.css('img').each do |img|
next if %r{\Ahttps?://} =~ img.attributes['src']
img.attributes['src'].value = Rails.application.routes.url_helpers.site_static_file_url(site, file: img.attributes['src'].value)
end
# Notificar a les usuaries que están viendo una previsualización
# XXX: Asume que estamos usando Bootstrap :B
html.at_css('body').first_element_child.before("<div class=\"alert alert-warning text-center\">#{I18n.t('posts.preview_message')}</div>")
# Cacofonía
html.to_html.html_safe
end
end
# Devuelve una llave para poder guardar el post en una cache # Devuelve una llave para poder guardar el post en una cache
def cache_key def cache_key
'posts/' + uuid.value 'posts/' + uuid.value

View file

@ -146,6 +146,13 @@ class Site < ApplicationRecord
File.join(Site.site_path, name_was) File.join(Site.site_path, name_was)
end end
# Limpiar la ruta y unirla con el separador de directorios del
# sistema operativo. Como si algún día fuera a cambiar o
# soportáramos Windows :P
def relative_path(suspicious_path)
File.join(path, *suspicious_path.gsub('..', '/').gsub('./', '').squeeze('/').split('/'))
end
# Obtiene la lista de traducciones actuales # Obtiene la lista de traducciones actuales
# #
# Siempre tiene que tener algo porque las traducciones están # Siempre tiene que tener algo porque las traducciones están

View file

@ -18,6 +18,10 @@ class PostPolicy
post.site.usuarie?(usuarie) || post.usuaries.include?(usuarie) post.site.usuarie?(usuarie) || post.usuaries.include?(usuarie)
end end
def preview?
show?
end
def new? def new?
create? create?
end end

View file

@ -24,6 +24,11 @@ class SitePolicy
!current_role.temporal !current_role.temporal
end end
# Todes pueden ver los archivos
def static_file?
true
end
# Todes pueden crear nuevos sitios # Todes pueden crear nuevos sitios
def new? def new?
true true

View file

@ -11,6 +11,9 @@
= link_to t('posts.edit'), = link_to t('posts.edit'),
edit_site_post_path(@site, @post.id), edit_site_post_path(@site, @post.id),
class: 'btn btn-block' class: 'btn btn-block'
= link_to t('posts.preview'),
site_post_preview_path(@site, @post.id),
class: 'btn btn-block'
%table.table.table-condensed %table.table.table-condensed
%thead %thead

View file

@ -68,6 +68,9 @@ Rails.application.configure do
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :letter_opener config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: 'localhost', config.action_mailer.default_url_options = { host: 'panel.sutty.local', port: 3000, protocol: 'https' }
port: 3000 }
Rails.application.routes.default_url_options[:host] = 'panel.sutty.local'
Rails.application.routes.default_url_options[:port] = 3000
Rails.application.routes.default_url_options[:protocol] = 'https'
end end

View file

@ -126,7 +126,8 @@ Rails.application.configure do
# Recibir por mail notificaciones de excepciones # Recibir por mail notificaciones de excepciones
config.action_mailer.default_url_options = { config.action_mailer.default_url_options = {
host: "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}" host: "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}",
protocol: 'https'
} }
config.action_mailer.perform_deliveries = true config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
@ -144,4 +145,7 @@ Rails.application.configure do
sender_address: ENV['DEFAULT_FROM'], sender_address: ENV['DEFAULT_FROM'],
exception_recipients: ENV['EXCEPTION_TO'] exception_recipients: ENV['EXCEPTION_TO']
} }
Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}"
Rails.application.routes.default_url_options[:protocol] = 'https'
end end

View file

@ -9,10 +9,10 @@
Rails.application.config.content_security_policy do |policy| Rails.application.config.content_security_policy do |policy|
policy.default_src :self policy.default_src :self
# XXX: Varios scripts generan estilos en línea # XXX: Varios scripts generan estilos en línea
policy.style_src :self, :unsafe_inline policy.style_src :self, :unsafe_inline, :https
# Repetimos la default para poder saber cuál es la política en falta # Repetimos la default para poder saber cuál es la política en falta
policy.script_src :self policy.script_src :self
policy.font_src :self policy.font_src :self, :https
# XXX: Los íconos de Trix se cargan vía data: # XXX: Los íconos de Trix se cargan vía data:
policy.img_src :self, :data, :https policy.img_src :self, :data, :https
# Ya no usamos applets! # Ya no usamos applets!

View file

@ -3,6 +3,14 @@
String.include CoreExtensions::String::StripTags String.include CoreExtensions::String::StripTags
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
# Definir tags de Liquid que provienen de complementos para que siempre
# devuelvan contenido vacío.
%w[seo feed_meta turbolinks].each do |tag|
Liquid::Template.register_tag(tag, Jekyll::Tags::Empty)
end
Liquid::Template.register_tag('base', Jekyll::Tags::Base)
module ActionDispatch module ActionDispatch
# Redefinir el formateo de URLs de Rails para eliminar parámetros # Redefinir el formateo de URLs de Rails para eliminar parámetros
# selectivamente # selectivamente
@ -28,12 +36,6 @@ end
# #
# TODO: Aplicar monkey patches en otro lado... # TODO: Aplicar monkey patches en otro lado...
module Jekyll module Jekyll
Site.class_eval do
def setup
ensure_not_in_dest
end
end
Reader.class_eval do Reader.class_eval do
def retrieve_posts(_); end def retrieve_posts(_); end

View file

@ -483,6 +483,8 @@ en:
categories: 'Everything' categories: 'Everything'
index: 'Posts' index: 'Posts'
edit: 'Edit' edit: 'Edit'
preview: 'Preliminary version'
preview_message: 'This is a preliminary version, use the Publish changes button back on the panel to publish the article on your site.'
open: 'Tip: You can add new options by typing them and pressing Enter' open: 'Tip: You can add new options by typing them and pressing Enter'
private: '&#128274; The values of this field will remain private' private: '&#128274; The values of this field will remain private'
select: select:

View file

@ -492,6 +492,8 @@ es:
new: 'Agregar:' new: 'Agregar:'
index: 'Artículos' index: 'Artículos'
edit: 'Editar' edit: 'Editar'
preview: 'Versión preliminar'
preview_message: 'Esta es una versión preliminar, para que el artículo aparezca en tu sitio utiliza el botón Publicar cambios en el panel'
open: 'Nota: Puedes agregar más opciones a medida que las escribes y presionas Entrar' open: 'Nota: Puedes agregar más opciones a medida que las escribes y presionas Entrar'
private: '&#128274; Los valores de este campo serán privados' private: '&#128274; Los valores de este campo serán privados'
select: select:

View file

@ -30,6 +30,8 @@ Rails.application.routes.draw do
# alias en nginx sin tener que usar expresiones regulares para # alias en nginx sin tener que usar expresiones regulares para
# detectar el nombre del sitio. # detectar el nombre del sitio.
get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} } get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} }
# Obtener archivos estáticos desde el directorio público
get '/sites/:site_id/static_file/(*file)', to: 'sites#static_file', as: 'site_static_file', constraints: { site_id: %r{[^/]+} }
resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
# Gestionar actualizaciones del sitio # Gestionar actualizaciones del sitio
@ -53,7 +55,9 @@ Rails.application.routes.draw do
nested do nested do
scope '(:locale)' do scope '(:locale)' do
post :'posts/reorder', to: 'posts#reorder' post :'posts/reorder', to: 'posts#reorder'
resources :posts resources :posts do
get :preview, to: 'posts#preview'
end
end end
end end