Merge branch 'rails' into search-engine

This commit is contained in:
f 2021-05-14 12:53:35 -03:00
commit 7413f6e2aa
46 changed files with 419 additions and 352 deletions

View file

@ -48,6 +48,7 @@ gem 'jekyll-commonmark'
gem 'jekyll-images'
gem 'jekyll-include-cache'
gem 'sutty-liquid'
gem 'loaf'
gem 'lockbox'
gem 'mini_magick'
gem 'mobility'

View file

@ -342,6 +342,8 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
loaf (0.10.0)
railties (>= 3.2)
lockbox (0.6.4)
lograge (0.11.2)
actionpack (>= 4)
@ -465,7 +467,7 @@ GEM
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
recursero-jekyll-theme (0.1.2)
recursero-jekyll-theme (0.1.3)
jekyll (~> 4.0)
jekyll-data (~> 1.1)
jekyll-feed (~> 0.9)
@ -705,6 +707,7 @@ DEPENDENCIES
jekyll-include-cache
letter_opener
listen (>= 3.0.5, < 3.2)
loaf
lockbox
lograge
memory_profiler

View file

@ -363,6 +363,13 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
.text-column-#{$size} {
column-count: $size;
}
.line-clamp-#{$size} {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: $size;
-webkit-box-orient: vertical;
}
}
/*

View file

@ -23,8 +23,6 @@ class ApplicationController < ActionController::Base
redirect_to sites_path
end
def markdown; end
private
def uuid?(string)
@ -42,11 +40,21 @@ class ApplicationController < ActionController::Base
site
end
# Devuelve el idioma actual y si no lo encuentra obtiene uno por
# defecto.
#
# Esto se refiere al idioma de la interfaz, no de los artículos.
def current_locale(include_params: true, site: nil)
return params[:locale] if include_params && params[:locale].present?
current_usuarie&.lang || I18n.locale
end
# El idioma es el preferido por le usuarie, pero no necesariamente se
# corresponde con el idioma de los artículos, porque puede querer
# traducirlos.
def set_locale(&action)
I18n.with_locale(current_usuarie&.lang || I18n.default_locale, &action)
I18n.with_locale(current_locale(include_params: false), &action)
end
# Muestra una página 404

View file

@ -7,9 +7,14 @@ class PostsController < ApplicationController
before_action :authenticate_usuarie!
# TODO: Traer los comunes desde ApplicationController
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
breadcrumb 'sites.index', :sites_path, match: :exact
breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact
# Las URLs siempre llevan el idioma actual o el de le usuarie
def default_url_options
{ locale: params[:locale] || current_usuarie&.lang || I18n.locale }
{ locale: current_locale }
end
def index
@ -20,7 +25,7 @@ class PostsController < ApplicationController
# XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es
# más simple saber si hubo cambios.
if filter_params.present? || stale?(@site)
if filter_params.present? || stale?([current_usuarie, @site])
# Todos los artículos de este sitio para el idioma actual
@posts = @site.indexed_posts.where(locale: locale)
# De este tipo
@ -38,44 +43,34 @@ class PostsController < ApplicationController
end
def show
@site = find_site
@post = @site.posts(lang: locale).find params[:id]
authorize @post
@locale = locale
fresh_when @post
authorize post
breadcrumb post.title.value, ''
fresh_when post
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
authorize @post
render html: @post.render
render html: post.render
end
def new
authorize Post
@site = find_site
@post = @site.posts.build(lang: locale, layout: params[:layout])
@locale = locale
@post = site.posts.build(lang: locale, layout: params[:layout])
breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), ''
end
def create
authorize Post
@site = find_site
service = PostService.new(site: @site,
service = PostService.new(site: site,
usuarie: current_usuarie,
params: params)
@post = service.create
if @post.persisted?
@site.touch
site.touch
forget_content
redirect_to site_post_path(@site, @post)
@ -85,30 +80,24 @@ class PostsController < ApplicationController
end
def edit
@site = find_site
@post = @site.posts(lang: locale).find params[:id]
authorize @post
@locale = locale
authorize post
breadcrumb post.title.value, site_post_path(site, post, locale: locale), match: :exact
breadcrumb 'posts.edit', ''
end
def update
@site = find_site
@post = @site.posts(lang: locale).find params[:id]
authorize post
authorize @post
service = PostService.new(site: @site,
post: @post,
service = PostService.new(site: site,
post: post,
usuarie: current_usuarie,
params: params)
if service.update.persisted?
@site.touch
site.touch
forget_content
redirect_to site_post_path(@site, @post)
redirect_to site_post_path(site, post)
else
render 'posts/edit'
end
@ -116,34 +105,30 @@ class PostsController < ApplicationController
# Eliminar artículos
def destroy
@site = find_site
@post = @site.posts(lang: locale).find params[:id]
authorize post
authorize @post
service = PostService.new(site: @site,
post: @post,
service = PostService.new(site: site,
post: post,
usuarie: current_usuarie,
params: params)
# TODO: Notificar si se pudo o no
service.destroy
@site.touch
redirect_to site_posts_path(@site)
site.touch
redirect_to site_posts_path(site, locale: post.lang.value)
end
# Reordenar los artículos
def reorder
@site = find_site
authorize @site
authorize site
service = PostService.new(site: @site,
service = PostService.new(site: site,
usuarie: current_usuarie,
params: params)
service.reorder
@site.touch
redirect_to site_posts_path(@site)
site.touch
redirect_to site_posts_path(site, locale: site.default_locale)
end
# Devuelve el idioma solicitado a través de un parámetro, validando
@ -154,7 +139,7 @@ class PostsController < ApplicationController
# solicite a le usuarie crear el nuevo idioma y que esto lo agregue al
# _config.yml del sitio en lugar de mezclar idiomas.
def locale
@site&.locales&.find(-> { I18n.locale }) do |l|
@locale ||= site&.locales&.find(-> { site&.default_locale }) do |l|
l.to_s == params[:locale]
end
end
@ -172,6 +157,14 @@ class PostsController < ApplicationController
#
# @return [Hash]
def filter_params
@filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_,v| v.present? }
@filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_, v| v.present? }
end
def site
@site ||= find_site
end
def post
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
end
end

View file

@ -7,6 +7,9 @@ class SitesController < ApplicationController
before_action :authenticate_usuarie!
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
breadcrumb 'sites.index', :sites_path, match: :exact
# Ver un listado de sitios
def index
authorize Site
@ -20,10 +23,12 @@ class SitesController < ApplicationController
def show
authorize site
redirect_to site_posts_path(site)
redirect_to site_posts_path(site, locale: site.default_locale)
end
def new
breadcrumb 'sites.new', :new_site_path
@site = Site.new
authorize @site
@ -35,7 +40,7 @@ class SitesController < ApplicationController
params: site_params)
if (@site = service.create).persisted?
redirect_to site_posts_path(@site)
redirect_to site_posts_path(@site, locale: @site.default_locale)
else
render 'new'
end
@ -43,6 +48,10 @@ class SitesController < ApplicationController
def edit
authorize site
breadcrumb site.title, site_posts_path(site, locale: site.default_locale), match: :exact
breadcrumb 'sites.edit', site_path(site)
SiteService.new(site: site).build_deploys
end
@ -53,7 +62,7 @@ class SitesController < ApplicationController
usuarie: current_usuarie)
if service.update.valid?
redirect_to site_posts_path(site)
redirect_to site_posts_path(site, locale: site.default_locale)
else
render 'edit'
end
@ -65,7 +74,7 @@ class SitesController < ApplicationController
# XXX: Convertir en una máquina de estados?
DeployJob.perform_async site.id if site.enqueue!
redirect_to site_posts_path(site)
redirect_to site_posts_path(site, locale: site.default_locale)
end
def reorder_posts
@ -85,7 +94,7 @@ class SitesController < ApplicationController
flash[:danger] = I18n.t('errors.posts.reorder')
end
redirect_to site_posts_path(site)
redirect_to site_posts_path(site, locale: site.default_locale)
end
def fetch

View file

@ -7,12 +7,18 @@ class UsuariesController < ApplicationController
include Pundit
before_action :authenticate_usuarie!
# TODO: Traer los comunes desde ApplicationController
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
breadcrumb 'sites.index', :sites_path, match: :exact
breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact
# Mostrar todes les usuaries e invitades de un sitio
def index
@site = find_site
site_usuarie = SiteUsuarie.new(@site, current_usuarie)
site_usuarie = SiteUsuarie.new(site, current_usuarie)
authorize site_usuarie
breadcrumb 'usuaries.index', ''
@policy = policy(site_usuarie)
end
@ -156,4 +162,8 @@ class UsuariesController < ApplicationController
'invitade'
end
end
def site
@site ||= find_site
end
end

View file

@ -30,13 +30,15 @@ class BacktraceJob < ApplicationJob
# Encuentra el código fuente del error
source = data.dig('sourcesContent', data['sources']&.index(backtrace['file']))&.split("\n")
backtrace['function'] = source[backtrace['line'] - 1] if source.present?
# XXX: Elimina la sangría aunque cambie las columnas porque
# eso lo vamos a ver en el archivo fuente directo.
backtrace['function'] = source[backtrace['line'] - 1].strip if source.present?
end
end
end
begin
raise BacktraceException, "#{origin}: #{params['errors']&.first&.dig('message')}"
raise BacktraceException, "#{origin}: #{message}"
rescue BacktraceException => e
ExceptionNotifier.notify_exception(e, data: { site: site.name, params: params, _backtrace: true })
end
@ -102,4 +104,9 @@ class BacktraceJob < ApplicationJob
rescue URI::Error
params.dig('context', 'url')
end
# @return [String,Nil]
def message
@message ||= params['errors']&.first&.dig('message')
end
end

View file

@ -16,6 +16,7 @@ class MetadataFile < MetadataTemplate
def validate
super
errors << I18n.t("metadata.#{type}.site_invalid") if site.invalid?
errors << I18n.t("metadata.#{type}.path_required") if path_missing?
errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description?

View file

@ -7,7 +7,6 @@
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
:value, :help, :required, :errors, :post,
:layout, keyword_init: true) do
# Determina si el campo es indexable
def indexable?
false
@ -25,10 +24,16 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
@cache_key ||= "post/#{post.uuid.value}/#{name}"
end
# Genera una versión de caché en base a la fecha de modificación del
# Post, el valor actual y los valores posibles, de forma que cualquier
# cambio permita renovar la caché.
#
# @return [String]
def cache_version
value.hash.to_s + values.hash.to_s
post.cache_version + value.hash.to_s + values.hash.to_s
end
# @return [String]
def cache_key_with_version
"#{cache_key}-#{cache_version}"
end

View file

@ -177,30 +177,17 @@ class Site < ApplicationRecord
end
alias default_lang default_locale
def read?
@read ||= false
end
# Lee el sitio y todos los artículos
def read
# No hacer nada si ya se leyó antes
return if read?
@jekyll.read
@read = true
end
# Trae los datos del directorio _data dentro del sitio
#
# XXX: Leer directamente sin pasar por Jekyll
def data
read
unless @jekyll.data.present?
@jekyll.reader.read_data
# Define los valores por defecto según la llave buscada
@jekyll.data.default_proc = proc do |data, key|
data[key] = case key
when 'layout' then {}
end
# Define los valores por defecto según la llave buscada
@jekyll.data.default_proc = proc do |data, key|
data[key] = case key
when 'layout' then {}
end
end
end
@jekyll.data
@ -209,7 +196,10 @@ class Site < ApplicationRecord
# Traer las colecciones. Todos los artículos van a estar dentro de
# colecciones.
def collections
read
unless @read
@jekyll.reader.read_collections
@read = true
end
@jekyll.collections
end
@ -223,8 +213,6 @@ class Site < ApplicationRecord
#
# @param lang: [String|Symbol] traer los artículos de este idioma
def posts(lang: nil)
read
# Traemos los posts del idioma actual por defecto o el que haya
lang ||= locales.include?(I18n.locale) ? I18n.locale : default_locale
lang = lang.to_sym

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,4 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('.index'), sites_path), t('.title')]
- breadcrumb 'sites.index', sites_path
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: nil
= content_for :body do
- 'black-bg'

View file

@ -3,7 +3,7 @@
# <%= error['type'] %>: <%= error['message'] %>
```
<%= Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values).map(&:strip) %>
<%= Terminal::Table.new headings: error['backtrace'].first.keys, rows: error['backtrace'].map(&:values) %>
```
<% end %>

View file

@ -1,7 +0,0 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
link_to(@site.name, site_path(@site)),
t('i18n.index'),
t('i18n.edit')]
= render 'i18n/form'

View file

@ -3,21 +3,14 @@
= inline_svg_tag 'sutty.svg', class: 'black', aria: true,
title: t('svg.sutty.title'), desc: t('svg.sutty.desc')
- if crumbs
%nav{ aria: { label: t('.title') }, role: 'navigation' }
%ol.breadcrumb
%li.breadcrumb-item
= link_to edit_usuarie_registration_path,
data: { toggle: 'tooltip' },
title: t('help.usuarie.edit') do
= current_usuarie.email
- crumbs.compact.each do |crumb|
- if crumb == crumbs.last
%li.breadcrumb-item.active{ aria: { current: 'page' } }
= crumb
%nav{ aria: { label: t('.title') } }
%ol.breadcrumb.m-0.flex-wrap
- breadcrumb_trail do |crumb|
%li.breadcrumb-item{ class: crumb.current? ? 'active' : '' }
- if crumb.current?
%span.line-clamp-1{ aria: { current: 'page' } }= crumb.name
- else
%li.breadcrumb-item= crumb
%span.line-clamp-1= link_to crumb.name, crumb.url
- if current_usuarie
%ul.navbar-nav

View file

@ -20,6 +20,7 @@
%body{ class: yield(:body) }
.container-fluid#sutty
= render 'layouts/breadcrumb'
= yield
- if flash[:js]
.js-flash.d-none{ data: flash[:js] }

View file

@ -43,7 +43,7 @@
- metadata = post[attribute]
- type = metadata.type
- cache metadata do
- cache [metadata, I18n.locale] do
= render("posts/attributes/#{type}",
base: 'post', post: post, attribute: attribute,
metadata: metadata, site: site,

View file

@ -1,10 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
link_to(@site.name, site_posts_path(@site)),
link_to(t('posts.index'), site_posts_path(@site)),
link_to(@post.title.value, site_post_path(@site, @post.id)),
t('posts.edit')]
.row.justify-content-center
.col-md-8
= render 'posts/form', site: @site, post: @post

View file

@ -1,10 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
@site.name,
link_to(t('posts.index'),
site_posts_path(@site)),
@category]
%main.row
%aside.menu.col-md-3
%h1= link_to @site.title, @site.url
@ -94,10 +87,7 @@
-#
TODO: Solo les usuaries cachean porque tenemos que separar
les botones por permisos.
TODO: Verificar qué pasa cuando se gestiona el sitio en
distintos idiomas a la vez
- cache_if @usuarie, post do
- cache_if @usuarie, [post, I18n.locale] do
- checkbox_id = "checkbox-#{post.id}"
%tr{ id: post.id, data: { target: 'reorder.row' } }
%td

View file

@ -1,9 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
@site.name,
link_to(t('posts.index'),
site_posts_path(@site)), t('posts.new')]
.row.justify-content-center
.col-md-8
= render 'posts/form', site: @site, post: @post

View file

@ -1,9 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
@site.name,
link_to(t('posts.index'), site_posts_path(@site)),
@post.title.value]
- dir = t("locales.#{@locale}.dir")
.row.justify-content-center
.col-md-8
@ -28,7 +22,7 @@
- metadata = @post[attr]
- next unless metadata.front_matter?
- cache metadata do
- cache [metadata, I18n.locale] do
= render("posts/attribute_ro/#{metadata.type}",
post: @post, attribute: attr,
metadata: metadata,
@ -42,6 +36,6 @@
- metadata = @post[attr]
- next if metadata.front_matter?
- cache metadata do
- cache [metadata, I18n.locale] do
%section.editor{ id: attr, dir: dir }
= @post.public_send(attr).to_s.html_safe

View file

@ -1,6 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
t('.title', site: @site.name)]
.row.justify-content-center
.col-md-8
%h1= t('.title', site: @site.name)

View file

@ -1,6 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')]
.row.justify-content-center
.col-md-8#pull
%h1= t('.title')

View file

@ -1,5 +1,3 @@
= render 'layouts/breadcrumb', crumbs: [t('sites.index.title')]
%main.row
%aside.col-md-3
%h1= t('.title')
@ -20,12 +18,12 @@
-#
TODO: Solo les usuaries cachean porque tenemos que separar
les botones por permisos.
- cache_if (rol.usuarie? && !rol.temporal), site do
- cache_if (rol.usuarie? && !rol.temporal), [site, I18n.locale] do
%tr
%td
%h2
- if policy(site).show?
= link_to site.title, site_path(site)
= link_to site.title, site_posts_path(site, locale: site.default_locale)
- else
= site.title
%p.lead= site.description

View file

@ -1,6 +1,3 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path), t('.title')]
.row.justify-content-center
.col-md-8
%h1= t('.title')

View file

@ -1,32 +1,24 @@
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
link_to(@site.name, @site),
t('.title')]
.row
.col
.row.justify-content-center
.col.col-md-8
%h1= t('.title')
.row
.col
-# Una tabla de usuaries y otra de invitades, con acciones
- %i[usuaries invitades].each do |u|
%h2
= t(".#{u}")
.btn-group{ role: 'group', 'aria-label': t('.actions') }
- if @policy.invite?
= link_to t('.invite'),
site_usuaries_invite_path(@site, invite_as: u.to_s),
class: 'btn',
data: { toggle: 'tooltip' },
title: t('.help.invite', invite_as: u.to_s)
- if policy(Collaboration.new(@site)).collaborate?
= link_to t('.public_invite'),
site_collaborate_path(@site),
class: 'btn',
data: { toggle: 'tooltip' },
title: t('.help.public_invite')
%p= t(".help.#{u}")
%h2.mt-5= t(".#{u}")
.btn-group{ role: 'group', 'aria-label': t('.actions') }
- if @policy.invite?
= link_to t('.invite'),
site_usuaries_invite_path(@site, invite_as: u.to_s),
class: 'btn',
data: { toggle: 'tooltip' },
title: t('.help.invite', invite_as: u.to_s)
- if policy(Collaboration.new(@site)).collaborate?
= link_to t('.public_invite'),
site_collaborate_path(@site),
class: 'btn',
data: { toggle: 'tooltip' },
title: t('.help.public_invite')
%p.lead= t(".help.#{u}")
%table.table.table-condensed
%tbody
- @site.send(u).each do |cuenta|

View file

@ -1,17 +1,9 @@
- invite_as = t("usuaries.invite_as.#{params[:invite_as]}")
= render 'layouts/breadcrumb',
crumbs: [link_to(t('sites.index.title'), sites_path),
@site.name,
link_to(t('posts.index'), site_usuaries_path(@site)),
t('.title', invite_as: invite_as)]
.row
.col
.row.justify-content-center
.col.col-md-8
%h1= t('.title', invite_as: invite_as)
.row
.col
= form_with url: site_usuaries_invite_path(@site), local: true do |f|
= f.hidden_field :invited_as, value: params[:invite_as].singularize
.form-group

View file

@ -64,4 +64,13 @@ Rails.application.configure do
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
config.middleware.use ExceptionNotification::Rack,
error_grouping: true,
email: {
email_prefix: '',
sender_address: ENV.fetch('DEFAULT_FROM', 'noreply@sutty.nl'),
exception_recipients: ENV.fetch('EXCEPTION_TO', 'errors@sutty.nl'),
normalize_subject: true
}
end

View file

@ -32,31 +32,52 @@ module ActionDispatch
end
# Lazy Loading de Jekyll, deshabilitando la instanciación de elementos
# que no necesitamos
# que no necesitamos. Esto permite que podamos leer el sitio por partes
# en lugar de todo junto.
#
# TODO: Aplicar monkey patches en otro lado...
module Jekyll
Reader.class_eval do
# No necesitamos otros posts
def retrieve_posts(_); end
# No necesitamos otros directorios
def retrieve_dirs(_, _, _); end
# No necesitamos las páginas
def retrieve_pages(_, _); end
# No necesitamos los archivos estáticos
def retrieve_static_files(_, _); end
# Solo lee los datos
def read_data
@site.data = DataReader.new(site).read(site.config['data_dir'])
end
# Lee todos los artículos del sitio
def read_collections
read_directories
read_included_excludes
sort_files!
CollectionReader.new(site).read
end
end
# No necesitamos los archivos de la plantilla
ThemeAssetsReader.class_eval do
def read; end
end
# Prevenir la lectura del documento
# Aplazar la lectura del documento
Document.class_eval do
alias_method :read!, :read
def read; end
end
# https://github.com/jekyll/jekyll/pull/8425
# Prevenir la instanciación de Time
#
# @see {https://github.com/jekyll/jekyll/pull/8425}
Utils.class_eval do
def parse_date(input, msg = 'Input could not be parsed.')
@parse_date_cache ||= {}
@ -79,3 +100,14 @@ module PgSearch
end
end
end
# JekyllData::Reader del plugin jekyll-data modifica Jekyll::Site#reader
# para también leer los datos que vienen en el theme.
module JekyllData
Reader.class_eval do
def read_data
super
read_theme_data
end
end
end

View file

@ -102,7 +102,7 @@ es:
update: Actualizar mi perfil
we_need_your_current_password_to_confirm_your_changes: Necesitamos tu contraseña actual para confirmar los cambios.
new:
sign_up: Registrarme por primera vez
sign_up: Registrarme
help: Para registrarte solo pedimos una dirección de correo y una contraseña. La contraseña se almacena de forma segura, ¡nadie más que vos la sabe! Recibirás un correo de confirmación de cuenta.
signed_up: Bienvenide. Tu cuenta fue creada.
signed_up_but_inactive: Tu cuenta ha sido creada correctamente. Sin embargo, no hemos podido iniciar la sesión porque tu cuenta aún no está activada.
@ -124,8 +124,7 @@ es:
forgot_your_password: "¿Has olvidado tu contraseña?"
sign_in: Iniciar sesión
sign_in_with_provider: Iniciar sesión con %{provider}
sign_up: Registrarme por primera vez
i_dont_have_account: ¿Nunca te registraste en LUNAR?
sign_up: Crear cuenta
i_have_account: ¿Ya tenés cuenta?
minimum_password_length:
one: "%{count} caracter como mínimo."

View file

@ -37,10 +37,12 @@ en:
metadata:
cant_be_empty: 'This field cannot be empty'
image:
site_invalid: 'The image cannot be stored if the site configuration is not valid'
not_an_image: 'Not an image'
path_required: 'Missing image for upload'
no_file_for_description: "Description with no associated image"
file:
site_invalid: 'The file cannot be stored if the site configuration is not valid'
path_required: "Missing file for upload"
no_file_for_description: "Description with no associated file"
event:
@ -82,10 +84,6 @@ en:
title: Link to www
success: Success!
error: Error
deploy_private:
title: Private version
success: Success!
error: Error
deploy_zip:
title: Build ZIP file
success: Available for download
@ -94,6 +92,10 @@ en:
title: Host as Tor Hidden Service
success: Success!
error: Error
deploy_private:
title: Private version
success: Success!
error: Error
deploy_alternative_domain:
title: Alternative domain name
success: Success!
@ -121,6 +123,7 @@ en:
models:
usuarie: User
licencia: License
design: Design
attributes:
usuarie:
email: 'E-mail address'
@ -128,27 +131,22 @@ en:
password_confirmation: 'Password confirmation'
current_password: 'Current password'
lang: 'Main language'
remember_me: Remember me
site:
name: 'Name'
title: 'Title'
description: 'Description'
colaboracion_anonima: Enable anonymous collaboration
acepta_invitades: Enable collaboration
colaboracion_anonima: Enable anonymous collaboration
contact: Enable contact forms
tienda_url: Store URL
tienda_api_key: Store access key
errors:
models:
site:
attributes:
deploys:
deploy_local_presence: 'We need to be build the site!'
invitadx:
attributes:
email:
taken: 'This e-mail address is already taken, please choose a different one'
password_confirmation:
confirmation: "The passwords don't match"
acepta_politicas_de_privacidad:
no_acepta_politicas_de_privacidad: "Please read and accept the privacy policy"
design_id:
layout_incompatible:
error: "Design can't be changed because there are posts with incompatible layouts"
@ -180,30 +178,6 @@ en:
usuarie:
edit: Edit my profile
category: 'Category'
i18n:
top: 'Back to top'
index: "Here is where you edit the text on your site that doesn't belong to a post, such as its description, sections, buttons... If you change languages up there in the title to be the same, you can edit them. If they're different, you can translate from one into the other."
count: 'This is the amount of texts.'
toc: 'Jump to this section'
meta: 'Metadata'
navegacion: 'Navigation'
inicio: 'Home'
volver: 'Back'
entrar: 'Enter'
cerrar: 'Close'
anchor: 'Internal links'
nav: 'Menu'
nav-lang: 'Language menu'
modulos: 'Modules'
header: 'Header'
sobre: 'About'
metodologia: 'Methodology'
planeando_recursos: 'Planning resources'
rutas: 'Agendas'
complementarios: 'Materials'
recursos: 'Resources'
contacta: 'Contact us'
agradecimientos: 'Acknowledgments'
sites:
index: 'This is the list of sites you can edit.'
enqueued: "The site is on queue to be generated. Once this
@ -215,6 +189,7 @@ en:
invitations:
accept: "Someone invited you to collaborate on their site. If you accept the invitation, you can access the site's edit mode."
reject: "If you decline, you won't have access."
pull: 'You have pending upgrades!'
close: 'Close help'
deploys:
deploy_local:
@ -319,15 +294,19 @@ en:
new:
title: 'Create site'
submit: 'Create site'
help: 'You can edit any of these options after site creation.'
edit:
title: 'Edit %{site}'
submit: 'Save changes'
btn: 'Configuration'
form:
errors:
title: There were errors and we couldn't save your changes :(
help: Please, look for the invalid fields to fix them
help:
name: "The name of your site. It can only include numbers and letters."
title: 'The title can be anything you want'
description: 'You site description that appears in search engines. Between 50 and 160 characters.'
design: 'Select the design for your site. You can change it later. We add more designs from time to time!'
licencia: 'Everything we publish has automatic copyright. This
means nobody can use our works without explicit permission. By
@ -398,6 +377,7 @@ en:
ar: 'Arabic'
posts:
empty: "There are no results for those search parameters."
caption: Post list
attribute_ro:
file:
download: Download file
@ -436,7 +416,7 @@ en:
reorder:
submit: 'Save order'
select: 'Select this post'
unselect: 'Deselected all'
unselect: 'Deselect all'
top: 'Send to top'
bottom: 'Send to bottom'
up: 'Up'
@ -480,6 +460,10 @@ en:
blank: Nothing
destroy: Delete
confirm_destroy: Are you sure?
form:
errors:
title: There are some errors on the form
help: Please, verify that all values are correct.
usuaries:
invite_as:
usuaries: users
@ -579,3 +563,14 @@ en:
local_invalid: "format is incorrect"
not_allowed: "that email provider is not welcome here"
server_not_available: "remote email server not available"
loaf:
breadcrumbs:
sites:
index: 'My sites'
new: 'Create'
edit: 'Configure'
posts:
new: 'New %{layout}'
edit: 'Editing'
usuaries:
index: 'Users'

View file

@ -37,10 +37,12 @@ es:
metadata:
cant_be_empty: 'El campo no puede estar vacío'
image:
site_invalid: 'La imagen no se puede almacenar si la configuración del sitio no es válida'
not_an_image: 'No es una imagen'
path_required: 'Se necesita una imagen'
no_file_for_description: 'Se envió una descripción sin imagen asociada'
file:
site_invalid: 'El archivo no se puede almacenar si la configuración del sitio no es válida'
path_required: 'Se necesita un archivo'
no_file_for_description: 'se envió una descripción sin archivo asociado'
event:
@ -176,45 +178,8 @@ es:
usuarie:
edit: Editar mi perfil
category: 'Categoría'
i18n:
top: 'Volver al principio'
index: 'Aquí puedes editar todos los textos del sitio que no se
corresponden con artículos, como la descripción, secciones, textos
de botones... Si cambias los idiomas arriba para que coincidan,
puedes editar los textos en el mismo idioma. Si los idiomas no
coinciden, puedes traducirlos de uno a otro.'
count: 'Esta es la cantidad de textos.'
toc: 'Saltar hasta esta sección'
meta: 'Metadata'
navegacion: 'Navegación'
inicio: 'Inicio'
volver: 'Volver'
entrar: 'Entrar'
cerrar: 'Cerrar'
anchor: 'Links internos'
nav: 'Menú'
nav-lang: 'Menú de idiomas'
modulos: 'Módulos'
header: 'Portada'
sobre: 'Acerca'
metodologia: 'Metodología'
planeando_recursos: 'Planeando recursos'
rutas: 'Rutas'
complementarios: 'Materiales complementarios'
recursos: 'Recursos'
contacta: 'Contacta'
agradecimientos: 'Agradecimientos'
sesion: 'Sesiones'
sesiones: 'Sesiones'
anexo: 'Anexo'
simple: 'Simple'
sites:
index: 'Este es el listado de sitios que puedes editar.'
edit_translations: 'Puedes editar los textos que salen en tu sitio
que no corresponden a artículos aquí, además de traducirlos a
otros idiomas.'
edit_posts: 'Aquí verás el listado de todos los artículos y podrás
editarlos o crear nuevos'
enqueued: 'El sitio está en la cola de espera para ser generado.
Una vez que este proceso termine, recibirás un correo indicando el
estado y si todo fue bien, se publicarán los cambios en tu sitio
@ -222,52 +187,11 @@ es:
enqueue: 'Cuando termines de hacer cambios en tu sitio, puedes
publicarlos con esta acción. Al finalizar recibirás un correo
avisándote cómo fue todo.'
build_log: 'Este es el registro de lo que sucedió mientras se
generaba el sitio. Si hubo algún problema, saldrá aquí.'
invitade: 'Les invitades a un sitio solo pueden crear y modificar entradas propias y no pueden publicar sin la revisión de une usuarie'
invitations:
accept: 'Alguien te invitó a colaborar en su sitio. Si aceptas la invitación, tendrás acceso a este sitio.'
reject: 'Si rechazas la invitación, no tendrás acceso.'
pull: 'Tienes actualizaciones pendientes :)'
close: 'Cerrar ayuda'
markdown:
intro: 'El formato del texto se llama Markdown. Es un formato
simple que puede ser escrito recordando unas pocas reglas y que
luego puede ser convertido a una página web, o un PDF, o un ePub.
Puedes usar los botones para dar formato básico a tu texto. Para
cuestiones más avanzadas, aquí tienes un <a
href="/markdown">recordatorio de markdown</a>.'
back: 'Volver'
input: 'Si escribimos...'
output: 'Obtenemos...'
bold: 'Negrita'
italic: 'Énfasis'
heading: 'Título'
link:
text: 'Un vínculo'
url: 'https://ejemplo.org'
quote: 'Un recorte de texto que nos gustó'
ul: 'Lista de cosas para hacer'
ol: 'Pasos para un plan maquiavélico'
img:
text: 'La isla de Kéfir'
url: 'https://kefir.red/images/isla.png'
ltr: 'Introducción'
rtl: 'مقدمة'
dir: 'Esta sintaxis puede ser complicada. Si quieres mezclar
expresiones en idiomas que usan otra dirección, como usar una
expresión en árabe en medio de un texto en castellano, o
viceversa, tienes que especificar la dirección y el idioma de esta
manera. De otra forma verás palabras fuera de orden,
especialmente en la versión en PDF.'
distraction_free_html: 'Puedes escribir sin distracciones presionando el botón <span class="fa fa-expand"></span>'
preview_html: 'Utiliza el botón de <span class="fa fa-search"></span> previsualización para ver el texto generado'
autocomplete_html: 'Algunos de estos campos se autocompletan. Si ya
sabes qué quieres poner, solo empieza a escribir y el autocompletado
te sugerirá las opciones disponibles. Si no existe lo que quieres
poner, termina de escribir y presiona <kbd>Entrar</kbd> para agregar
opciones que aun no existen. Para vaciar las opciones, usa el botón
&times; a la derecha.'
deploys:
deploy_local:
title: 'Alojar en Sutty'
@ -646,3 +570,14 @@ es:
local_invalid: "el formato es incorrecto"
not_allowed: "no es bienvenida"
server_not_available: "el proveedor no está disponible"
loaf:
breadcrumbs:
sites:
index: 'Mis sitios'
new: 'Crear'
edit: 'Configurar'
posts:
new: 'Nuevo %{layout}'
edit: 'Editando'
usuaries:
index: 'Usuaries'

View file

@ -8,8 +8,6 @@ Rails.application.routes.draw do
root 'application#index'
get 'markdown', to: 'application#markdown'
constraints(Constraints::ApiSubdomain.new) do
scope module: 'api' do
namespace :v1 do
@ -33,7 +31,8 @@ Rails.application.routes.draw do
# detectar el nombre del sitio.
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{[^/]+} }
get '/sites/:site_id/static_file/(*file)', to: 'sites#static_file', as: 'site_static_file',
constraints: { site_id: %r{[^/]+} }
get '/env.js', to: 'env#index'
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]

View file

@ -0,0 +1,5 @@
class ChangeBytesToBigInt < ActiveRecord::Migration[6.1]
def change
change_column :build_stats, :bytes, :bigint
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_05_07_221120) do
ActiveRecord::Schema.define(version: 2021_05_11_211357) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@ -170,7 +170,7 @@ ActiveRecord::Schema.define(version: 2021_05_07_221120) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "deploy_id"
t.integer "bytes"
t.bigint "bytes"
t.float "seconds"
t.string "action", null: false
t.text "log"

View file

@ -6,12 +6,12 @@
disabled: true
description_en: "Upload your own theme. [This feature is in development, help us!](https://sutty.nl/en/#contact)"
description_es: "Subir tu propio diseño. [Esta posibilidad está en desarrollo, ¡ayudanos!](https://sutty.nl/#contacto)"
- name_en: 'I want you to create a site for me'
- name_en: 'I want you to develop a site for me'
name_es: 'Quiero que desarrollen mi sitio'
gem: 'sutty-theme-custom'
url: 'https://sutty.nl'
disabled: true
description_en: "If you want us to create your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)"
description_en: "If you want us to develop your site, you're welcome to [contact us!](https://sutty.nl/en/#contact) :)"
description_es: "Si querés que desarrollemos tu sitio, [escribinos](https://sutty.nl/#contacto) :)"
- name_en: 'Minima'
name_es: 'Mínima'
@ -24,11 +24,11 @@
name_es: 'Sutty'
gem: 'sutty-jekyll-theme'
url: 'https://rubygems.org/gems/sutty-jekyll-theme/'
description_en: "Sutty's design"
description_en: "The Sutty design"
description_es: 'El diseño de Sutty'
license: 'https://0xacab.org/sutty/jekyll/sutty-jekyll-theme/-/blob/master/LICENSE.txt'
credits_es: 'Sutty es parte de la economía solidaria :)'
credits_en: 'Sutty is a solidarity economy project :)'
credits_en: 'Sutty is a solidarity economy project!'
- name_en: 'Self-managed Book Publisher'
name_es: 'Editorial Autogestiva'
gem: 'editorial-autogestiva-jekyll-theme'
@ -42,11 +42,11 @@
name_es: 'Donaciones'
gem: 'sutty-donaciones-jekyll-theme'
url: 'https://donaciones.sutty.nl/'
description_en: "Make your own donations campaign with payment buttons."
description_en: "Make your own donation campaign with payment buttons."
description_es: 'Realizá campañas de donaciones con botones de pago.'
license: 'https://0xacab.org/sutty/jekyll/sutty-donaciones-jekyll-theme/-/blob/master/LICENSE.txt'
credits_es: 'Diseñamos esta plantilla para [visibilizar campañas de donaciones](https://sutty.nl/plantilla-para-donaciones/) durante la cuarentena.'
credits_en: 'We designed this theme to increase [requests for donations visibility](https://sutty.nl/template-for-donations/) during the quarantine.'
credits_en: 'We designed this theme to increase [visibility for donation requests](https://sutty.nl/template-for-donations/) during the quarantine.'
- name_en: 'Support campaign'
name_es: 'Adhesiones'
gem: 'adhesiones-jekyll-theme'
@ -55,29 +55,29 @@
description_es: 'Realizá campañas de adhesión.'
license: 'https://0xacab.org/sutty/jekyll/adhesiones-jekyll-theme/-/blob/master/LICENSE.txt'
credits_es: 'Desarrollamos esta plantilla junto con [Librenauta](https://sutty.nl/plantilla-para-campa%C3%B1as-de-adhesiones/)'
credits_en: 'We made this template with Librenauta :)'
credits_en: 'This template was made in collaboration with Librenauta'
designer_url: 'https://copiona.com/donaunbit/'
- name_en: 'Community Radio'
name_es: 'Radio comunitaria'
gem: 'radios-comunitarias-jekyll-theme'
url: 'https://radio.sutty.nl/'
description_en: "A theme with streaming support, designed for community radios"
description_en: "A theme with live streaming support, designed for community radios"
description_es: 'Con soporte para transmisión en vivo, pensada para radios comunitarias'
license: 'https://0xacab.org/sutty/jekyll/radios-comunitarias-jekyll-theme/-/blob/master/LICENSE.txt'
credits_es: 'Desarrollamos esta plantilla junto con Librenauta en 15 horas :)'
credits_en: 'We made this template with Librenauta in 15 hours :)'
credits_en: 'This template was made in collaboration with Librenauta in 15 hours!'
designer_url: 'https://copiona.com/donaunbit/'
- name_en: 'Resource toolkit'
name_es: 'Recursero'
gem: 'recursero-jekyll-theme'
url: 'https://recursero.info/'
disabled: true
description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)"
description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)"
description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)"
- name_en: 'Other themes'
name_es: 'Mi propio diseño'
gem: 'sutty-theme-own'
url: 'https://jekyllthemes.org'
disabled: true
description_en: "We're working to add more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)"
description_en: "We're working towards adding more themes for you to use. [Contact us!](https://sutty.nl/en/#contact)"
description_es: "Estamos trabajando para que puedas tener más diseños. [¡Escribinos!](https://sutty.nl/#contacto)"

View file

@ -104,7 +104,7 @@
description_en: "This license gives everyone the freedom to use,
adapt, and redistribute the contents of your site by requiring
attribution only. We recommend this license if you're publishing
articles that require maximum diffusion, even in commercial media, but
articles that require maximum dissemination, even in commercial media, but
you want to be attributed. Users of the site will have to mention the
source and indicate if they made changes to it."
url_en: 'https://creativecommons.org/licenses/by/4.0/'
@ -198,13 +198,13 @@
url_es: 'https://creativecommons.org/licenses/by-sa/4.0/deed.es'
description_en: "This license is the same as the CC-BY 4.0 but it adds
a requirement of sharing the work and its derivatives under the same
license. This is a reciprocitary, _copyleft_, license that keeps
license. This is a reciprocal, _copyleft_, license that keeps
culture free. Though commercial uses are allowed, they must be shared
under the same license, so any modifications done for profit are free
as well."
description_es: "Esta licencia es igual que la CC-BY 4.0 con el
requisito agregado de compartir la obra y sus obras derivadas con la
misma licencia. Esta es una licencia reciprocitaria, _copyleft_, que
misma licencia. Esta es una licencia recíproca, _copyleft_, que
mantiene y profundiza la cultura libre. Aunque los usos comerciales
están permitidos, las mejoras hechas con fines de lucro deben ser
compartidas bajo la misma licencia."

View file

@ -0,0 +1,147 @@
# frozen_string_literal: true
require 'test_helper'
class DeployJobTest < ActiveSupport::TestCase
def site
@site ||= create :site
end
# Mockup
def job
job = BacktraceJob.new
job.instance_variable_set :@site, site
job.instance_variable_set :@params, notice
job
end
# setTimeout(() => { throw('Prueba') }, 1000)
def notice
@notice ||= {
'errors' => [
{
'type' => '',
'message' => 'Prueba',
'backtrace' => [
{
'function' => 'pt</e.prototype.notify',
'file' => 'https://tintalimon.com.ar/assets/js/pack.js',
'line' => 89,
'column' => 74_094
},
{
'function' => 'pt</e.prototype.onerror',
'file' => 'https://tintalimon.com.ar/assets/js/pack.js',
'line' => 89,
'column' => 74_731
},
{
'function' => 'pt</e.prototype._instrument/window.onerror',
'file' => 'https://tintalimon.com.ar/assets/js/pack.js',
'line' => 89,
'column' => 71_925
},
{
'function' => 'setTimeout handler*',
'file' => 'debugger eval code',
'line' => 1,
'column' => 11
}
]
}
],
'context' => {
'severity' => 'error',
'history' => [
{
'type' => 'error',
'target' => 'html. > head. > script.[type="text/javascript"][src="//stats.habitapp.org/piwik.js"]',
'date' => '2021-04-26T22:06:58.390Z'
},
{
'type' => 'DOMContentLoaded',
'target' => '[object HTMLDocument]',
'date' => '2021-04-26T22:06:58.510Z'
},
{
'type' => 'load',
'target' => '[object HTMLDocument]',
'date' => '2021-04-26T22:06:58.845Z'
},
{
'type' => 'xhr',
'date' => '2021-04-26T22:06:58.343Z',
'method' => 'GET',
'url' => 'assets/data/site.json',
'statusCode' => 200,
'duration' => 506
},
{
'type' => 'xhr',
'date' => '2021-04-26T22:06:58.886Z',
'method' => 'GET',
'url' => 'assets/templates/cart.html',
'statusCode' => 200,
'duration' => 591
}
],
'windowError' => true,
'notifier' => {
'name' => 'airbrake-js/browser',
'version' => '1.4.2',
'url' => 'https://github.com/airbrake/airbrake-js/tree/master/packages/browser'
},
'userAgent' => 'Mozilla/5.0 (Windows NT 6.1; rv:85.0) Gecko/20100101 Firefox/85.0',
'url' => 'https://tintalimon.com.ar/carrito/',
'rootDirectory' => 'https://tintalimon.com.ar',
'language' => 'JavaScript'
},
'params' => {},
'environment' => {},
'session' => {}
}
# XXX: Siempre devolvemos un duplicado porque BacktraceJob lo
# modifica
@notice.dup
end
# Asegurarse que el sitio se destruye al terminar de usarlo
teardown do
site&.destroy
end
test 'al recibir un backtrace enviamos un error' do
ActionMailer::Base.deliveries.clear
assert BacktraceJob.perform_now site_id: site.id, params: notice
email = ActionMailer::Base.deliveries.first
assert email
assert_equal ' (BacktraceJob::BacktraceException) "tintalimon.com.ar: Prueba"', email.subject
assert(%r{webpack://} =~ email.body.to_s)
end
test 'los errores se basan en un sitio' do
assert_equal site, job.send(:site)
end
test 'los errores tienen archivos fuente' do
assert_equal %w[https://tintalimon.com.ar/assets/js/pack.js], job.send(:sources)
end
test 'los errores tienen una url de origen' do
assert_equal 'tintalimon.com.ar', job.send(:origin)
end
test 'los errores tienen un sourcemap' do
local_job = job
sourcemap = local_job.send :sourcemap
assert_equal SourceMap::Map, sourcemap.class
assert_equal 'assets/js/pack.js', sourcemap.filename
assert sourcemap.sources.size.positive?
end
end