5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-05-21 00:20:48 +00:00

Merge branch 'rails' of 0xacab.org:sutty/sutty into issue-13177
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
f 2023-05-13 11:39:08 -03:00
commit bfdc97592c
34 changed files with 312 additions and 53 deletions

View file

@ -386,6 +386,9 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
}
}
.word-break-all { word-break: break-all !important; }
.hyphens { hyphens: auto; }
/*
* Modificadores de Bootstrap que no tienen versión responsive.
*/
@ -408,6 +411,8 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
.text-#{$grid-breakpoint}-right { text-align: right !important; }
.text-#{$grid-breakpoint}-center { text-align: center !important; }
.word-break-#{$grid-breakpoint}-all { word-break: break-all !important; }
// posición
@each $position in $positions {
.position-#{$grid-breakpoint}-#{$position} { position: $position !important; }

View file

@ -91,6 +91,10 @@ class ApplicationController < ActionController::Base
breadcrumb 'stats.index', root_path, match: :exact
end
def site
@site ||= find_site
end
protected
def configure_permitted_parameters

View file

@ -0,0 +1,41 @@
# frozen_string_literal: true
# La lista de estados de compilación, por ahora solo mostramos el último
# estado.
class BuildStatsController < ApplicationController
include ActionView::Helpers::NumberHelper
include ActionView::Helpers::DateHelper
before_action :authenticate_usuarie!
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
def index
authorize SiteBuildStat.new(site)
breadcrumb I18n.t('build_stats.index.title'), ''
@headers = %w[type url seconds size].map do |header|
t("deploy_mailer.deployed.th.#{header}")
end
@table = site.deployment_list.map do |deploy|
type = deploy.class.name.underscore
urls = deploy.respond_to?(:urls) ? deploy.urls : [deploy.url].compact
urls = [nil] if urls.empty?
build_stat = deploy.build_stats.where(status: true).last
seconds = build_stat&.seconds || 0
{
title: t("deploy_mailer.deployed.#{type}.title"),
urls: urls,
seconds: {
human: distance_of_time_in_words(seconds),
machine: "PT#{seconds}S"
},
size: number_to_human_size(build_stat&.bytes || 0, precision: 2)
}
end
end
end

View file

@ -159,10 +159,6 @@ class PostsController < ApplicationController
end.transform_keys(&:to_sym)
end
def site
@site ||= find_site
end
def post
@post ||= site.posts(lang: locale).find(params[:post_id] || params[:id])
end

View file

@ -96,6 +96,13 @@ class UsuariesController < ApplicationController
# XXX: La invitación tiene que ser enviada luego de crear el rol
if role.persisted?
# Si es una cuenta manual que no está confirmada aun,
# aprovechar para reconfirmarla.
if !usuarie.confirmed? && !usuarie.created_by_invite?
usuarie.confirmation_token = nil
usuarie.send :generate_confirmation_token!
end
usuarie.deliver_invitation
else
raise ArgumentError, role.errors.full_messages

View file

@ -7,7 +7,7 @@ class RenewDistributedPressTokensJob < ApplicationJob
# detener la tarea si algo pasa.
def perform
DistributedPressPublisher.with_about_to_expire_tokens.find_each do |publisher|
publisher.touch
publisher.save
rescue DistributedPress::V1::Error => e
data = { instance: publisher.instance, expires_at: publisher.client.token.expires_at }

View file

@ -80,25 +80,18 @@ class DeployDistributedPress < Deploy
# Devuelve las URLs de todos los protocolos
def urls
protocol_urls + gateway_urls
gateway_urls
end
private
# @return [Array]
def gateway_urls
remote_info.dig(:distributed_press, :links).values.map do |protocol|
remote_info.dig(:distributed_press, :links)&.values&.map do |protocol|
[ protocol[:link], protocol[:gateway] ]
end.flatten.compact.select do |link|
end&.flatten&.compact&.select do |link|
link.include? '://'
end
end
def protocol_urls
remote_info.dig(:distributed_press, :protocols).select do |_, enabled|
enabled
end.map do |protocol, _|
"#{protocol}://#{site.hostname}"
end
end || []
end
# El cliente de la API

View file

@ -27,8 +27,4 @@ class DeployFullRsync < DeployRsync
result.present? && result.all?
end
def url
"https://#{user_host.last}/"
end
end

View file

@ -38,6 +38,7 @@ class DeployRsync < Deploy
#
# @return [Boolean]
def ssh?
return true if destination.start_with? 'rsync://'
user, host = user_host
ssh_available = false

View file

@ -40,6 +40,72 @@ class Site
def not_published_yet?
build_stats.jekyll.where(status: true).count.zero?
end
# Cambios posibles luego de la última publicación exitosa:
#
# * Artículos modificados
# * Configuración modificada
# * Métodos de publicación añadidos
#
# @return [Boolean]
def awaiting_publication?
waiting? && (post_pending? || deploy_pending? || configuration_pending?)
end
# Se modificaron artículos después de publicar el sitio por última
# vez
#
# @return [Boolean]
def post_pending?
last_indexed_post_time > last_publication_time
end
# Se modificó el sitio después de publicarlo por última vez
#
# @return [Boolean]
def deploy_pending?
last_deploy_time > last_publication_time
end
# Se modificó la configuración del sitio
#
# @return [Boolean]
def configuration_pending?
last_configuration_time > last_publication_time
end
private
# Encuentra la fecha del último artículo modificado. Si no hay
# ninguno, devuelve la fecha de modificación del sitio.
#
# @return [Time]
def last_indexed_post_time
indexed_posts.order(updated_at: :desc).select(:updated_at).first&.updated_at || updated_at
end
# Encuentra la fecha de última modificación de los métodos de
# publicación.
#
# @return [Time]
def last_deploy_time
deploys.order(created_at: :desc).select(:created_at).first&.created_at || updated_at
end
# Encuentra la fecha de última publicación exitosa, si no hay
# ninguno, devuelve la fecha de modificación del sitio.
#
# @return [Time]
def last_publication_time
build_stats.jekyll.where(status: true).order(created_at: :desc).select(:created_at).first&.created_at || updated_at
end
# Fecha de última modificación de la configuración
#
# @return [Time]
def last_configuration_time
File.mtime(config.path)
end
end
end
end

View file

@ -0,0 +1,3 @@
# frozen_string_literal: true
SiteBuildStat = Struct.new(:site)

View file

@ -12,6 +12,8 @@ class Usuarie < ApplicationRecord
validates_with EmailAddress::ActiveRecordValidator, field: :email
before_create :lang_from_locale!
before_update :remove_confirmation_invitation_inconsistencies!
before_update :accept_invitation_after_confirmation!
has_many :roles
has_many :sites, through: :roles
@ -49,9 +51,31 @@ class Usuarie < ApplicationRecord
end
end
# Les usuaries necesitan link de invitación si no tenían cuenta
# y todavía no aceptaron la invitación anterior.
def needs_invitation_link?
created_by_invite? && !invitation_accepted?
end
private
def lang_from_locale!
self.lang = I18n.locale.to_s
end
# El invitation_token solo es necesario cuando fue creade por otre
# usuarie. De lo contrario lo que queremos es un proceso de
# confirmación.
def remove_confirmation_invitation_inconsistencies!
self.invitation_token = nil unless created_by_invite?
end
# Si le usuarie (re)confirma su cuenta con una invitación pendiente,
# considerarla aceptada también.
def accept_invitation_after_confirmation!
if confirmed?
self.invitation_token = nil
self.invitation_accepted_at ||= Time.now.utc
end
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
# Quiénes pueden ver estados de compilación de un sitio
class SiteBuildStatPolicy
attr_reader :site_build_stat, :usuarie
def initialize(usuarie, site_build_stat)
@usuarie = usuarie
@site_build_stat = site_build_stat
end
# Todes les usuaries e invitades de este sitio
def index?
site_build_stat.site.usuarie?(usuarie) || site_build_stat.site.invitade?(usuarie)
end
end

View file

@ -15,7 +15,8 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
add_role temporal: false, rol: 'usuarie'
site.deploys.build type: 'DeployLocal'
sync_nodes
# Los sitios de testing no se sincronizan
sync_nodes unless site.name.end_with? '.testing'
I18n.with_locale(usuarie.lang.to_sym || I18n.default_locale) do
# No se puede llamar a site.config antes de save porque el sitio
@ -215,7 +216,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
# Crea los deploys necesarios para sincronizar a otros nodos de Sutty
def sync_nodes
Rails.application.nodes.each do |node|
site.deploys.build(type: 'DeployFullRsync', destination: "sutty@#{node}:")
site.deploys.build(type: 'DeployFullRsync', destination: "rsync://rsyncd.#{node}/deploys/", hostname: node)
end
end

View file

@ -0,0 +1,20 @@
%main.row
%aside.menu.col-md-3
= render 'sites/header', site: @site
.col
%h1= t('.title')
%table.table
%thead
%tr
- @headers.each do |header|
%th{ scope: 'col' }= header
%tbody
- @table.each do |row|
- row[:urls].each do |url|
%tr
%th{ scope: 'row' }= row[:title]
%td= link_to_if url.present?, url, url, class: 'word-break-all'
%td
%time{ datetime: row[:seconds][:machine] }= row[:seconds][:human]
%td= row[:size]

View file

@ -8,7 +8,7 @@
%h1= site.title
%p= site.description
- if @resource.created_by_invite? && !@resource.invitation_accepted?
- if @resource.needs_invitation_link?
%p= link_to t('devise.mailer.invitation_instructions.accept'),
accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
@ -18,5 +18,7 @@
format: :'devise.mailer.invitation_instructions.accept_until_format'))
%p= t('devise.mailer.invitation_instructions.ignore')
- elsif !@resource.confirmed? && @resource.confirmation_token
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
- else
%p= link_to t('devise.mailer.invitation_instructions.sign_in'), root_url

View file

@ -9,7 +9,7 @@
\
= site.description
\
- if @resource.created_by_invite? && !@resource.invitation_accepted?
- if @resource.needs_invitation_link?
= accept_invitation_url(@resource, invitation_token: @token, change_locale_to: @resource.lang)
\
- if @resource.invitation_due_at
@ -18,6 +18,8 @@
format: :'devise.mailer.invitation_instructions.accept_until_format'))
\
= t('devise.mailer.invitation_instructions.ignore')
- elsif !@resource.confirmed? && @resource.confirmation_token
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
- else
= root_url(change_locale_to: @resource.lang)
= t('devise.mailer.invitation_instructions.sign_in')

View file

@ -19,6 +19,10 @@
= link_to t('.tienda'), @site.tienda_url,
role: 'button', class: 'btn'
%li.nav-item
= link_to t('.contact_us'), t('.contact_us_href'),
class: 'btn', rel: 'me', target: '_blank'
%li.nav-item
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
method: :delete, role: 'button', class: 'btn'

View file

@ -1,6 +1,8 @@
- invalid_help = site.config.fetch('invalid_help', t('.invalid_help'))
- sending_help = site.config.fetch('sending_help', t('.sending_help'))
.form-group
= submit_tag t('.save'), class: 'btn submit-post'
= render 'bootstrap/alert', class: 'invalid-help d-none' do
= site.config.fetch('invalid_help', t('.invalid_help'))
= invalid_help
= render 'bootstrap/alert', class: 'sending-help d-none' do
= site.config.fetch('sending_help', t('.sending_help'))
= sending_help

View file

@ -1,9 +1,10 @@
%main.row
%aside.menu.col-md-3
%h1= @site.title
%p.lead= @site.description
- cache_if @usuarie, [@site, I18n.locale] do
= render 'sites/status', site: @site
= render 'sites/header', site: @site
= render 'sites/status', site: @site
= render 'sites/build', site: @site, class: 'btn-block'
%h3= t('posts.new')
%table.table.table-sm.mb-3
@ -29,8 +30,6 @@
type: 'info',
link: site_usuaries_path(@site)
= render 'sites/build', site: @site
- if @site.design.credits
= render 'bootstrap/alert' do
= sanitize_markdown @site.design.credits
@ -45,7 +44,8 @@
- next if param == 'q'
%input{ type: 'hidden', name: param, value: value }
.form-group.flex-grow-0.m-0
%input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @filter_params[:q] }
%label.sr-only{for: 'q'}= t('.search')
%input#q.form-control.border.border-magenta{ type: 'search', placeholder: t('.search'), name: 'q', value: @filter_params[:q] }
%input.sr-only{ type: 'submit' }
- if @site.locales.size > 1

View file

@ -3,7 +3,7 @@
method: :post,
class: 'form-inline inline' do
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
class: 'btn no-border-radius',
class: "btn no-border-radius #{local_assigns[:class]}",
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
data: { disable_with: t('sites.enqueued') },
disabled: site.enqueued?

View file

@ -55,7 +55,7 @@
layouts: site.incompatible_layouts.to_sentence)
.row.row-cols-1.row-cols-md-2.designs
-# Demasiado complejo para un f.collection_radio_buttons
- Design.all.find_each do |design|
- Design.all.order(priority: :desc).each do |design|
.design.col.d-flex.flex-column
.custom-control.custom-radio
= f.radio_button :design_id, design.id,

View file

@ -0,0 +1,3 @@
.hyphens{ lang: site.default_locale }
%h1= site.title
%p.lead= site.description

View file

@ -1,7 +1,9 @@
- link = nil
- if site.not_published_yet?
- message = t('.not_published_yet')
- if site.building?
- elsif site.awaiting_publication?
- message = t('.awaiting_publication')
- elsif site.building?
- if site.average_publication_time_calculable?
- average_building_time = site.average_publication_time
- elsif !site.similar_sites?
@ -16,4 +18,4 @@
- link = true
= render 'bootstrap/alert' do
= link_to_if link, message.html_safe, site.url, class: 'alert-link'
= link_to_if link, message.html_safe, site_build_stats_path(site), class: 'alert-link'

View file

@ -22,7 +22,7 @@ es:
someone_invited_you: "Alguien te ha invitado a colaborar en %{url}, podés aceptar la invitación con el enlace a continuación."
accept: "Aceptar la invitación"
accept_until: "La invitación vencerá el %{due_date}."
ignore: "Si no querés aceptar la invitación, por favor ignora este correo. Tu cuenta no será creada hasta que aceptes la invitación y configures una contraseña."
ignore: "Si no querés aceptar la invitación, por favor ignorá este correo. Tu cuenta no será creada hasta que aceptes la invitación y configures una contraseña."
sign_in: "Iniciá sesión con tu cuenta para aceptar o rechazar la invitación."
time:
formats:

View file

@ -206,6 +206,8 @@ en:
title: 'Your location in Sutty'
logout: Log out
mutual_aid: Mutual aid
contact_us: "Contact us"
contact_us_href: "https://sutty.nl/en/#contact"
collaborations:
collaborate:
submit: Register
@ -302,7 +304,7 @@ en:
storage network may continue retaining copies of the data
indefinitely.
[Learn more](https://ffdweb.org/building-distributed-press-a-publishing-tool-for-the-decentralized-web/)
[Learn more](https://sutty.nl/learn-more-about-publish-to-dweb-functionality/)
stats:
index:
title: Statistics
@ -362,9 +364,10 @@ en:
static_file_migration: 'File migration'
find_and_replace: 'Search and replace'
status:
building: "Your site is building, please wait <time datetime=\"PT%{seconds}S\">%{average_time}</time> to refresh this page..."
building: "Your site is building, refresh this page in <time datetime=\"PT%{seconds}S\">%{average_time}</time>."
not_published_yet: "Your site is being published for the first time, please wait up to 1 minute..."
available: "Your site is available! Click here to visit it."
available: "Your site is available! Click here to find all the different ways to visit it."
awaiting_publication: "There are unpublished changes. Click the button below and wait a moment to find them on your site."
index:
title: 'My Sites'
pull: 'Upgrade'
@ -430,7 +433,7 @@ en:
title: 'Design'
actions: 'Information about this design'
url: 'Demo'
licencia: 'License'
license: 'License'
licencia:
title: 'License for the site and everything published on it'
url: 'Read the license'
@ -541,7 +544,8 @@ en:
new: 'Post types'
remove_filter_help: 'Remove the filter: %{filter}'
categories: 'Everything'
index: 'Posts'
index:
search: 'Search'
edit: 'Edit'
preview:
btn: 'Preliminary version'
@ -700,3 +704,6 @@ en:
filter:
filter: 'Filter'
remove: 'Back'
build_stats:
index:
title: "Publications"

View file

@ -206,6 +206,8 @@ es:
title: 'Tu ubicación en Sutty'
logout: Cerrar sesión
mutual_aid: Ayuda mutua
contact_us: "Contacto"
contact_us_href: "https://sutty.nl/#contacto"
collaborations:
collaborate:
submit: Registrarme
@ -307,7 +309,7 @@ es:
nodos en la red de almacenamiento distribuida puedan retener
copias de tu contenido indefinidamente.
[Saber más (en inglés)](https://ffdweb.org/building-distributed-press-a-publishing-tool-for-the-decentralized-web/)
[Saber más](https://sutty.nl/saber-mas-sobre-publicar-a-la-web-distribuida/)
stats:
index:
title: Estadísticas
@ -367,9 +369,10 @@ es:
static_file_migration: 'Migración de archivos'
find_and_replace: 'Búsqueda y reemplazo'
status:
building: "Tu sitio se está publicando, por favor espera <time datetime=\"PT%{seconds}S\">%{average_time}</time> para recargar esta página..."
building: "Tu sitio se está publicando, recargá esta página en <time datetime=\"PT%{seconds}S\">%{average_time}</time>."
not_published_yet: "Tu sitio se está publicando por primera vez, por favor espera hasta un minuto..."
available: "¡Tu sitio está disponible! Cliquea aquí para visitarlo."
available: "¡Tu sitio está disponible! Cliqueá aquí para encontrar todas las formas en que podés visitarlo."
awaiting_publication: "Hay cambios sin publicar, cliqueá el botón debajo y espera un momento para encontrarlos en tu sitio."
index:
title: 'Mis sitios'
pull: 'Actualizar'
@ -549,7 +552,8 @@ es:
categories: 'Todos'
new: 'Tipos de artículos'
remove_filter_help: 'Quitar este filtro: %{filter}'
index: 'Artículos'
index:
search: 'Buscar'
edit: 'Editar'
preview:
btn: 'Versión preliminar'
@ -708,3 +712,6 @@ es:
filter:
filter: 'Filtrar'
remove: 'Volver'
build_stats:
index:
title: "Publicaciones"

View file

@ -75,5 +75,7 @@ Rails.application.routes.draw do
get :'stats/host', to: 'stats#host'
get :'stats/uris', to: 'stats#uris'
get :'stats/resources', to: 'stats#resources'
resources :build_stats, only: %i[index]
end
end

View file

@ -0,0 +1,5 @@
class AddPriorityToDesigns < ActiveRecord::Migration[6.1]
def change
add_column :designs, :priority, :integer
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
# Envía los cambios a través de rsyncd
class ChangeFullRsyncDestination < ActiveRecord::Migration[6.1]
def up
DeployFullRsync.find_each do |deploy|
Rails.application.nodes.each do |node|
deploy.destination = "rsync://rsyncd.#{node}/deploys/"
deploy.save
end
end
end
def down
DeployFullRsync.find_each do |deploy|
Rails.application.nodes.each do |node|
deploy.destination = "sutty@#{node}:"
deploy.save
end
end
end
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
# Agrega la columna de nodo a los logs
class AddNodeToAccessLogs < ActiveRecord::Migration[6.1]
def change
add_column :access_logs, :node, :string, index: true
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_10_22_225449) do
ActiveRecord::Schema.define(version: 2023_04_15_153231) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@ -217,6 +217,7 @@ ActiveRecord::Schema.define(version: 2021_10_22_225449) do
t.boolean "disabled", default: false
t.text "credits"
t.string "designer_url"
t.integer "priority"
end
create_table "indexed_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@ -380,6 +381,7 @@ ActiveRecord::Schema.define(version: 2021_10_22_225449) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
create_trigger("indexed_posts_before_insert_update_row_tr", :compatibility => 1).
on("indexed_posts").
before(:insert, :update) do

View file

@ -6,6 +6,7 @@
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)"
priority: '0'
- name_en: 'I want you to develop a site for me'
name_es: 'Quiero que desarrollen mi sitio'
gem: 'sutty-theme-custom'
@ -13,6 +14,7 @@
disabled: true
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) :)"
priority: '2'
- name_en: 'Minima'
name_es: 'Mínima'
gem: 'sutty-minima'
@ -20,6 +22,7 @@
description_en: "Sutty Minima is based on [Minima](https://jekyll.github.io/minima/), a blog-focused theme for Jekyll."
description_es: 'Sutty Mínima es una plantilla para blogs basada en [Mínima](https://jekyll.github.io/minima/).'
license: 'https://0xacab.org/sutty/jekyll/minima/-/blob/master/LICENSE.txt'
priority: '100'
- name_en: 'Sutty'
name_es: 'Sutty'
gem: 'sutty-jekyll-theme'
@ -29,6 +32,7 @@
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!'
priority: '90'
- name_en: 'Self-managed Book Publisher'
name_es: 'Editorial Autogestiva'
gem: 'editorial-autogestiva-jekyll-theme'
@ -38,6 +42,7 @@
license: 'https://0xacab.org/sutty/jekyll/editorial-autogestiva-jekyll-theme/-/blob/master/LICENSE.txt'
credits_es: 'Esta plantilla fue inspirada en el trabajo de las [editoriales autogestivas](https://sutty.nl/plantillas-para-crear-cat%C3%A1logos-de-editoriales-autogestivas/)'
credits_en: 'This theme is inspired by [independent publishing projects](https://sutty.nl/en/new-template-for-publishing-projects/)'
priority: '50'
- name_en: 'Donations'
name_es: 'Donaciones'
gem: 'sutty-donaciones-jekyll-theme'
@ -47,6 +52,7 @@
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 [visibility for donation requests](https://sutty.nl/template-for-donations/) during the quarantine.'
priority: '80'
- name_en: 'Support campaign'
name_es: 'Adhesiones'
gem: 'adhesiones-jekyll-theme'
@ -57,6 +63,7 @@
credits_es: 'Desarrollamos esta plantilla junto con [Librenauta](https://sutty.nl/plantilla-para-campa%C3%B1as-de-adhesiones/)'
credits_en: 'This template was made in collaboration with Librenauta'
designer_url: 'https://copiona.com/donaunbit/'
priority: '60'
- name_en: 'Community Radio'
name_es: 'Radio comunitaria'
gem: 'radios-comunitarias-jekyll-theme'
@ -67,6 +74,7 @@
credits_es: 'Desarrollamos esta plantilla junto con Librenauta en 15 horas :)'
credits_en: 'This template was made in collaboration with Librenauta in 15 hours!'
designer_url: 'https://copiona.com/donaunbit/'
priority: '70'
- name_en: 'Resource toolkit'
name_es: 'Recursero'
gem: 'recursero-jekyll-theme'
@ -74,10 +82,12 @@
disabled: true
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'
priority: '3'
- name_en: 'More themes'
name_es: 'Más plantillas'
gem: 'sutty-theme-own'
url: 'https://jekyllthemes.org'
disabled: true
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)"
priority: '1'

View file

@ -19,7 +19,11 @@
<p>Gracias por ayudarnos a encontrar errores :)</p>
<p><a href="/">Volver al panel</a></p>
<p>
<a href="/">Volver al panel</a>
|
<a href="https://sutty.nl/#contacto" rel="me" target="_blank">Contáctanos</a>
</p>
</section>
<section class="col-10" lang="en">
@ -31,7 +35,11 @@
<p>Thanks for helping us in finding errors! :)</p>
<p><a href="/">Go back to panel</a></p>
<p>
<a href="/">Go back to panel</a>
|
<a href="https://sutty.nl/en/#contact" rel="me" target="_blank">Contact us</a>
</p>
</section>
</main>
</body>