diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c72a632d..8be521c7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,6 +46,7 @@ assets: - "rails" - "production.panel.sutty.nl" - "panel.sutty.nl" + - "panel.testing.sutty.nl" except: - "schedules" cache: diff --git a/Dockerfile b/Dockerfile index 394a81e5..a73a96cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,6 @@ RUN apk add npm && npm install -g pnpm@~7 && apk del npm COPY ./monit.conf /etc/monit.d/sutty.conf -RUN apk add npm && npm install -g pnpm && apk del npm - VOLUME "/srv" EXPOSE 3000 diff --git a/Gemfile b/Gemfile index 3677d738..59823dd0 100644 --- a/Gemfile +++ b/Gemfile @@ -118,9 +118,8 @@ group :development, :test do gem 'derailed_benchmarks' gem 'dotenv-rails' gem 'pry' - # Adds support for Capybara system testing and selenium driver - gem 'capybara', '~> 2.13' - gem 'selenium-webdriver', '~> 4.8.0' + gem 'capybara' + gem 'selenium-webdriver' gem 'sqlite3' end diff --git a/Gemfile.lock b/Gemfile.lock index 2adf2f1b..5cbecf5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,13 +118,15 @@ GEM bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) - capybara (2.18.0) + capybara (3.40.0) addressable + matrix mini_mime (>= 0.1.3) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (>= 2.0, < 4.0) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) chartkick (5.0.2) climate_control (1.2.0) coderay (1.1.3) @@ -360,13 +362,14 @@ GEM net-pop net-smtp marcel (1.0.2) + matrix (0.4.2) memory_profiler (1.0.1) mercenary (0.4.0) method_source (1.0.0) mini_histogram (0.3.1) mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) + mini_portile2 (2.8.6) minitest (5.21.1) mobility (1.2.9) i18n (>= 0.6.10, < 2) @@ -386,7 +389,7 @@ GEM net-ssh (7.2.1) netaddr (2.0.6) nio4r (2.7.0-x86_64-linux-musl) - nokogiri (1.16.0-x86_64-linux-musl) + nokogiri (1.16.5-x86_64-linux-musl) mini_portile2 (~> 2.8.2) racc (~> 1.4) orm_adapter (0.5.0) @@ -407,7 +410,7 @@ GEM pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.4) + public_suffix (5.0.5) puma (6.4.2-x86_64-linux-musl) nio4r (~> 2.0) pundit (2.3.1) @@ -486,7 +489,7 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.9.2) redis (>= 4, < 6) - regexp_parser (2.9.0) + regexp_parser (2.9.2) request_store (1.5.1) rack (>= 1.4) responders (3.1.1) @@ -542,7 +545,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt - selenium-webdriver (4.8.6) + selenium-webdriver (4.9.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -632,7 +635,7 @@ DEPENDENCIES bootstrap (~> 4) brakeman bundler-audit - capybara (~> 2.13) + capybara chartkick commonmarker concurrent-ruby-ext @@ -707,7 +710,7 @@ DEPENDENCIES safe_yaml safely_block (~> 0.3.0) sassc-rails - selenium-webdriver (~> 4.8.0) + selenium-webdriver sourcemap spring spring-watcher-listen diff --git a/Procfile b/Procfile index a74f613b..d3d8207d 100644 --- a/Procfile +++ b/Procfile @@ -1,13 +1,6 @@ -migrate: bundle exec rake db:prepare db:seed -sutty: bundle exec puma config.ru -blazer_5m: bundle exec rake blazer:run_checks SCHEDULE="5 minutes" -blazer_1h: bundle exec rake blazer:run_checks SCHEDULE="1 hour" -blazer_1d: bundle exec rake blazer:run_checks SCHEDULE="1 day" -blazer: bundle exec rake blazer:send_failing_checks -prometheus: bundle exec prometheus_exporter -b 0.0.0.0 --prefix "sutty_" -distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew cleanup: bundle exec rake cleanup:everything +distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7 -stats: bundle exec rake stats:process_all que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que +stats: bundle exec rake stats:process_all fediblock: bundle exec rails activity_pub:fediblocks diff --git a/_sites/.keep b/_sites/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/_sites/_storage/.keep b/_sites/_storage/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 4d1d0848..94f37b4f 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -20,6 +20,8 @@ $form-feedback-valid-color: $black; $form-feedback-invalid-color: $magenta; $form-feedback-icon-valid-color: $black; $component-active-bg: $magenta; +$btn-white-space: nowrap; +$font-weight-bolder: 700; $spacers: ( 2-plus: 0.75rem @@ -51,7 +53,7 @@ $sizes: ( .editor { .editor-content { figure { - border: 1px solid transparentize($magenta, 0.3) + border: 1px solid transparentize($magenta, 0.3); } } } @@ -87,6 +89,26 @@ $sizes: ( box-shadow: 0 0 0 0.2rem $cyan; } } + + a.black { + color: $white; + + &:active { + color: var(--color); + } + } + + form.was-validated { + div.border { + div.custom-control { + div { + .custom-control-label { + color: $white; + } + } + } + } + } } // TODO: Encontrar la forma de generar esto desde los locales de Rails @@ -135,6 +157,10 @@ a { color: var(--color); } + &:focus { + outline: 1px solid var(--color); + } + &[target=_blank] { /* TODO: Convertir a base64 para no hacer peticiones extra */ &:after { @@ -143,6 +169,8 @@ a { } } + + $footer-height: 60px; /* Colores */ @@ -256,6 +284,10 @@ svg { } } +.badge { + white-space: break-spaces; +} + .btn-sm { @extend .badge } @@ -527,6 +559,7 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1); color: var(--#{$color}); } + ::-moz-selection, ::selection { background: var(--#{$color}); @@ -551,6 +584,7 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1); a { color: var(--#{$color}); } + } } @@ -620,4 +654,19 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1); } } } +} + + +hr { + border-bottom: 1px solid #dee2e6; } + +a.black { + &:active { + color: var(--color); + } + &:focus { + color: var(--color); + } +} + diff --git a/app/assets/stylesheets/dark.scss b/app/assets/stylesheets/dark.scss index f7f3a09d..a04360f9 100644 --- a/app/assets/stylesheets/dark.scss +++ b/app/assets/stylesheets/dark.scss @@ -31,4 +31,17 @@ $cyan: #13fefe; } } +a.black { + color: $white; + &:hover { + color: var(--color); + } + &:active { + color: var(--color); + } + &:focus { + color: var(--color); + } +} + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 117be995..a96d1ec0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -86,7 +86,9 @@ class ApplicationController < ActionController::Base end def site - @site ||= find_site + @site ||= find_site.tap do |s| + s.reindex_changes! + end end protected diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 057c3068..2d5c5a1e 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -55,9 +55,11 @@ class PostsController < ApplicationController def new authorize Post - @post = site.posts(lang: locale).build(layout: params[:layout]) - breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), '' + layout = site.layouts[params[:layout].to_sym] + @post = Post.build(locale: locale, layout: layout, site: site) + + breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: layout.humanized_name.downcase), '' end def create @@ -155,13 +157,13 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v| + @filter_params ||= params.permit(:q, :category, :page, layout: []).to_hash.select do |_, v| v.present? end.transform_keys(&:to_sym) end def post - @post ||= site.posts(lang: locale).find(params[:post_id] || params[:id]) + @post ||= site.indexed_posts.find_by!(locale: locale, path: params[:post_id] || params[:id]).post end # Recuerda el nombre del servicio de subida de archivos diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index c2c7bc58..893a16c9 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -209,7 +209,7 @@ class StatsController < ApplicationController # # @return [Integer] def nodes - @nodes ||= ENV.fetch('NODES', 1).to_i + @nodes ||= Rails.application.nodes.size + 1 end def period diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fcbd4074..684c5a7f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,6 +19,25 @@ module ApplicationHelper [root, name] end + # Devuelve los params sin el valor para una llave, detectando si el + # valor es un array. + # + # @param filtering_params [Hash] + # @param key [Symbol,String] + # @param value [Any] + def filter_params_by(filtering_params, key, value) + filtering_params.map do |k, v| + if k == key + case v + when Array then [k, v - [value]] + else nil + end + else + [ k, v ] + end + end.compact.to_h + end + def plain_field_name_for(*names) root, name = field_name_for(*names) diff --git a/app/jobs/deploy_job.rb b/app/jobs/deploy_job.rb index 66cccd1b..b91f4d0d 100644 --- a/app/jobs/deploy_job.rb +++ b/app/jobs/deploy_job.rb @@ -77,6 +77,8 @@ class DeployJob < ApplicationJob t << ([type.to_s] + row.values) end end) + rescue DeployTimedOutException => e + notify_exception e ensure if site.present? site.update status: 'waiting' diff --git a/app/jobs/git_pull_job.rb b/app/jobs/git_pull_job.rb index 72e20be0..30431495 100644 --- a/app/jobs/git_pull_job.rb +++ b/app/jobs/git_pull_job.rb @@ -1,18 +1,29 @@ # frozen_string_literal: true -# Permite traer los cambios desde webhooks - +# Permite traer los cambios desde el repositorio remoto class GitPullJob < ApplicationJob # @param :site [Site] # @param :usuarie [Usuarie] + # @param :message [String] # @return [nil] - def perform(site, usuarie) + def perform(site, usuarie, message) @site = site return unless site.repository.origin - return unless site.repository.fetch.positive? - site.repository.merge(usuarie) + site.repository.fetch + + return if site.repository.up_to_date? + + if site.repository.fast_forward? + site.repository.fast_forward! + else + site.repository.merge(usuarie, message) + end + + site.repository.git_lfs_checkout site.reindex_changes! + + nil end end diff --git a/app/models/concerns/metadata/inverse_concern.rb b/app/models/concerns/metadata/inverse_concern.rb new file mode 100644 index 00000000..aa300fa7 --- /dev/null +++ b/app/models/concerns/metadata/inverse_concern.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Metadata + module InverseConcern + extend ActiveSupport::Concern + + included do + # Hay una relación inversa? + # + # @return [Boolean] + def inverse? + inverse.present? + end + + # La relación inversa + # + # @return [Nil,Symbol] + def inverse + @inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym + end + end + end +end diff --git a/app/models/deploy_distributed_press.rb b/app/models/deploy_distributed_press.rb index bbd5a9a0..d2c6ec5f 100644 --- a/app/models/deploy_distributed_press.rb +++ b/app/models/deploy_distributed_press.rb @@ -23,7 +23,7 @@ class DeployDistributedPress < Deploy # # @param :output [Bool] # @return [Bool] - def deploy + def deploy(output: true) status = false log = [] diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index 29a31f8c..a3b783ca 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -9,8 +9,8 @@ class DeployLocal < Deploy def bundle(output: false) run %(bundle config set --local clean 'true'), output: output - run(%(bundle config set --local deployment 'true'), output: output) if site.gemfile_lock_path? - run %(bundle config set --local path '#{gems_dir}'), output: output + run %(bundle config set --local deployment 'true'), output: output if site.gemfile_lock_path? + run %(bundle config set --local path '#{site.bundle_path}'), output: output run %(bundle config set --local without 'test development'), output: output run %(bundle config set --local cache_all 'false'), output: output run %(bundle install), output: output diff --git a/app/models/deploy_reindex.rb b/app/models/deploy_reindex.rb deleted file mode 100644 index f3eb3d23..00000000 --- a/app/models/deploy_reindex.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# Reindexa los artículos al terminar la compilación -class DeployReindex < Deploy - def deploy(**) - time_start - - site.reset - - Site.transaction do - site.indexed_posts.destroy_all - site.index_posts! - end - - time_stop - - build_stats.create action: 'reindex', - log: 'Reindex', - seconds: time_spent_in_seconds, - bytes: size, - status: true - site.touch - end - - def size - 0 - end - - def limit - 1 - end - - def hostname; end - - def url; end - - def destination; end -end diff --git a/app/models/deploy_rsync.rb b/app/models/deploy_rsync.rb index fcc5a65d..5ebea162 100644 --- a/app/models/deploy_rsync.rb +++ b/app/models/deploy_rsync.rb @@ -81,7 +81,7 @@ class DeployRsync < Deploy # # @return [Array] def user_host - destination.split(':', 2).first.split('@', 2).tap do |d| + @user_host ||= destination.split(':', 2).first.split('@', 2).tap do |d| next unless d.size == 1 d.insert(0, nil) diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index 184cd05f..992c07c8 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -34,15 +34,66 @@ class IndexedPost < ApplicationRecord scope :in_category, ->(category) { where("front_matter->'categories' ? :category", category: category.to_s) } scope :by_usuarie, ->(usuarie) { where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } + # Trae todos los valores únicos para un atributo + # + # @param :attribute [String,Symbol] + # @return [Array] + scope :everything_of, ->(attribute) do + where('front_matter ? :attribute', attribute: attribute) + .pluck( + Arel.sql( + ActiveRecord::Base::sanitize_sql(['front_matter -> :attribute', attribute: attribute]) + ) + ) + .flatten.uniq + end + + validates_presence_of :layout, :path, :locale + belongs_to :site - # Encuentra el post original + # La ubicación del Post en el disco # - # @return [nil,Post] - def post - return if post_id.blank? + # @return [String] + def full_path + @full_path ||= File.join(site.path, "_#{locale}", "#{path}.markdown") + end - @post ||= site.posts(lang: locale).find(post_id, uuid: true) + # La colección + # + # @return [Jekyll::Collection] + def collection + site.collections[locale.to_s] + end + + # Obtiene el documento + # + # @return [Jekyll::Document] + def document + @document ||= Jekyll::Document.new(full_path, site: site.jekyll, collection: collection) + end + + # El Post + # + # @todo Decidir qué pasa si el archivo ya no existe + # @return [Post] + def post + @post ||= Post.new(document: document, site: site, layout: schema) + end + + # Devuelve el esquema de datos + # + # @todo Renombrar + # @return [Layout] + def schema + site.layouts[layout.to_sym] + end + + # Existe físicamente? + # + # @return [Boolean] + def exist? + File.exist?(full_path) end # Convertir locale a direccionario de PG diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index be1fa670..93a1f710 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -3,6 +3,8 @@ # Almacena el UUID de otro Post y actualiza el valor en el Post # relacionado. class MetadataBelongsTo < MetadataRelatedPosts + include Metadata::InverseConcern + # TODO: Convertir algunos tipos de valores en módulos para poder # implementar varios tipos de campo sin repetir código # @@ -20,82 +22,12 @@ class MetadataBelongsTo < MetadataRelatedPosts document.data[name.to_s] end - def validate - super - - errors << I18n.t('metadata.belongs_to.missing_post') unless post_exists? - - errors.empty? - end - - # Guardar y guardar la relación inversa también, eliminando la - # relación anterior si existía. - def save - super - - # Si no hay relación inversa, no hacer nada más - return true unless changed? - return true unless inverse? - - # Si estamos cambiando la relación, tenemos que eliminar la relación - # anterior - if belonged_to.present? - belonged_to[inverse].value = belonged_to[inverse].value.reject do |rej| - rej == post.uuid.value - end - end - - # No duplicar las relaciones - belongs_to[inverse].value = (belongs_to[inverse].value.dup << post.uuid.value) unless belongs_to.blank? || included? - - true - end - - # El Post actual está incluido en la relación inversa? - def included? - belongs_to[inverse].value.include?(post.uuid.value) - end - - # Hay una relación inversa y el artículo existe? - def inverse? - inverse.present? - end - - # El campo que es la relación inversa de este - def inverse - @inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym - end - - # El Post relacionado con este artículo - def belongs_to - posts.find(value, uuid: true) if value.present? - end - - # El artículo relacionado anterior - def belonged_to - posts.find(value_was, uuid: true) if value_was.present? - end - - def related_posts? - true - end - - def related_methods - @related_methods ||= %i[belongs_to belonged_to].freeze - end - def indexable_values - belongs_to&.title&.value + posts.find_by_post_uuid(value).try(:title) end private - def post_exists? - return true if sanitize(value).blank? - - sanitize(value).present? && belongs_to.present? - end - def sanitize(uuid) uuid.to_s.gsub(/[^a-f0-9\-]/i, '') end diff --git a/app/models/metadata_content.rb b/app/models/metadata_content.rb index 444ee2fe..0fc32221 100644 --- a/app/models/metadata_content.rb +++ b/app/models/metadata_content.rb @@ -58,8 +58,13 @@ class MetadataContent < MetadataTemplate uri = URI element['src'] - # No permitimos recursos externos - raise URI::Error unless Rails.application.config.hosts.include?(uri.hostname) + # No permitimos recursos externos, solo si sabemos cuales son + # los recursos locales + if Rails.application.config.hosts.present? + unless Rails.application.config.hosts.include?(uri.hostname) + raise URI::Error + end + end element['src'] = convert_src_to_internal_path uri diff --git a/app/models/metadata_has_and_belongs_to_many.rb b/app/models/metadata_has_and_belongs_to_many.rb index 2c4f3d43..2c1b0f96 100644 --- a/app/models/metadata_has_and_belongs_to_many.rb +++ b/app/models/metadata_has_and_belongs_to_many.rb @@ -1,46 +1,5 @@ # frozen_string_literal: true -# Establece una relación de muchos a muchos artículos. Cada campo es un -# Array de UUID que se mantienen sincronizados. -# -# Por ejemplo: -# -# Un libro puede tener muches autores y une autore muchos libros. La -# relación has_many tiene que traer todes les autores relacionades con -# el libro actual. La relación belongs_to tiene que traer todes les -# autores que tienen este libro. La relación es bidireccional, no hay -# diferencia entre has_many y belongs_to. +# Establece una relación de muchos a muchos artículos class MetadataHasAndBelongsToMany < MetadataHasMany - # Mantiene la relación inversa si existe. - # - # La relación belongs_to se mantiene actualizada en la modificación - # actual. Lo que buscamos es mantener sincronizada esa relación. - # - # Buscamos en belongs_to la relación local, si se eliminó hay que - # quitarla de la relación remota, sino hay que agregarla. - # - def save - # XXX: No usamos super - self[:value] = sanitize value - - return true unless changed? - return true unless inverse? - - # XXX: Usamos asignación para aprovechar value= que setea el valor - # anterior en @value_was - (had_many - has_many).each do |remove| - remove[inverse].value = remove[inverse].value.reject do |rej| - rej == post.uuid.value - end - end - - (has_many - had_many).each do |add| - next unless add[inverse] - next if add[inverse].value.include? post.uuid.value - - add[inverse].value = (add[inverse].value.dup << post.uuid.value) - end - - true - end end diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb index 13f0dcf5..c5f01f7c 100644 --- a/app/models/metadata_has_many.rb +++ b/app/models/metadata_has_many.rb @@ -6,55 +6,5 @@ # Localmente tenemos un Array de UUIDs. Remotamente tenemos una String # apuntando a un Post, que se mantiene actualizado como el actual. class MetadataHasMany < MetadataRelatedPosts - # Todos los Post relacionados - def has_many - return default_value if value.blank? - - posts.where(uuid: value) - end - - # La relación anterior - def had_many - return default_value if value_was.blank? - - posts.where(uuid: value_was) - end - - def inverse? - inverse.present? - end - - # La relación inversa - # - # @return [Nil,Symbol] - def inverse - @inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym - end - - # Actualizar las relaciones inversas. Hay que buscar la diferencia - # entre had y has_many. - def save - super - - return true unless changed? - return true unless inverse? - - (had_many - has_many).each do |remove| - remove[inverse]&.value = remove[inverse].default_value - end - - (has_many - had_many).each do |add| - add[inverse]&.value = post.uuid.value - end - - true - end - - def related_posts? - true - end - - def related_methods - @related_methods ||= %i[has_many had_many].freeze - end + include Metadata::InverseConcern end diff --git a/app/models/metadata_has_one.rb b/app/models/metadata_has_one.rb new file mode 100644 index 00000000..0cadb1d9 --- /dev/null +++ b/app/models/metadata_has_one.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +class MetadataHasOne < MetadataBelongsTo; end diff --git a/app/models/metadata_locales.rb b/app/models/metadata_locales.rb index 37b50286..b4ddf485 100644 --- a/app/models/metadata_locales.rb +++ b/app/models/metadata_locales.rb @@ -6,11 +6,16 @@ class MetadataLocales < MetadataHasAndBelongsToMany # # @return { lang: { title: uuid } } def values - @values ||= site.locales.map do |locale| - [locale, posts.where(lang: locale).map do |post| - [title(post), post.uuid.value] - end.to_h] - end.to_h + @values ||= other_locales.to_h do |other_locale| + [ + other_locale, + posts.where(locale: other_locale).pluck(:title, :layout, :post_id).to_h do |row| + row.tap do |value| + value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})" + end + end + ] + end end # Siempre hay una relación inversa @@ -33,17 +38,13 @@ class MetadataLocales < MetadataHasAndBelongsToMany # # @return [Array] def other_locales - site.locales.reject do |locale| - locale == post.lang.value.to_sym - end + @other_locales ||= site.locales - [locale] end # Obtiene todos los posts de los otros locales con el mismo layout # - # @return [PostRelation] + # @return [IndexedPost::ActiveRecord_AssociationRelation] def posts - other_locales.map do |locale| - site.posts(lang: locale).where(layout: post.layout.value) - end.reduce(&:concat) || PostRelation.new(site: site, lang: 'any') + site.indexed_posts(locale: other_locales).where(layout: post.layout.value).where.not(post_id: post.uuid.value) end end diff --git a/app/models/metadata_order.rb b/app/models/metadata_order.rb index 1b33a388..f63a7d49 100644 --- a/app/models/metadata_order.rb +++ b/app/models/metadata_order.rb @@ -5,7 +5,7 @@ class MetadataOrder < MetadataTemplate # El valor según la posición del post en la relación ordenada por # fecha, a fecha más alta, posición más alta def default_value - super || site.posts(lang: lang).sort_by(:date).index(post) + super || ((site.indexed_posts.where(locale: locale).first&.order || 0) + 1) end def save diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 42d1381b..6d52096e 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -3,14 +3,16 @@ # Devuelve una lista de títulos y UUID de todos los posts del mismo # idioma que el actual, para usar con input-map.js class MetadataRelatedPosts < MetadataArray - # Genera un Hash de { title | slug => uuid } y excluye el Post actual + # Genera un Hash de { title (schema) => uuid } para usar en + # options_for_select + # # @return [Hash] def values - @values ||= posts.map do |p| - next if p.uuid.value == post.uuid.value - - [title(p), p.uuid.value] - end.compact.to_h + @values ||= posts.pluck(:title, :created_at, :layout, :post_id).to_h do |row| + row.tap do |value| + value[0] = "#{value[0]} #{value.delete_at(1).strftime('%F')} (#{site.layouts[value.delete_at(1)].humanized_name})" + end + end end # Las relaciones nunca son privadas @@ -23,23 +25,23 @@ class MetadataRelatedPosts < MetadataArray end def indexable_values - posts.where(uuid: value).map(&:title).map(&:value) + posts.where(post_id: value).pluck(:title) end private - # Obtiene todos los posts y opcionalmente los filtra + # Obtiene todos los posts menos el actual y opcionalmente los filtra + # + # @return [IndexedPost::ActiveRecord_AssociationRelation] def posts - site.posts(lang: lang).where(**filter) + site.indexed_posts.where(locale: locale).where.not(post_id: post.uuid.value).where(filter) end - def title(post) - "#{post&.title&.value || post&.slug&.value} #{post&.date&.value.strftime('%F')} (#{post.layout.humanized_name})" - end - - # Encuentra el filtro + # Encuentra el filtro desde el esquema del atributo + # + # @return [Hash,nil] def filter - layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys || {} + layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys end def sanitize(uuid) diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 78989e15..a95f7e12 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -62,20 +62,28 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, end # Trae el idioma actual del sitio o del panel + # + # @deprecated Empezar a usar locale # @return [String] def lang - @lang ||= post&.lang&.value || I18n.locale + @lang ||= post&.lang&.value || I18n.locale.to_s end - # El valor por defecto + alias_method :locale, :lang + + # El valor por defecto desde el esquema de datos + # + # @return [any] def default_value - layout.metadata.dig(name, 'default', lang.to_s) + layout.metadata.dig(name, 'default', lang) end # Valores posibles, busca todos los valores actuales en otros # artículos del mismo sitio + # + # @return [Array] def values - site.everything_of(name, lang: lang) + site.indexed_posts.everything_of(name) end # Valor actual o por defecto. Al memoizarlo podemos modificarlo diff --git a/app/models/post.rb b/app/models/post.rb index 8885897f..6307e259 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -30,15 +30,44 @@ class Post # a demanda? def find_layout(path) File.foreach(path).lazy.grep(/^layout: /).take(1).first&.split(' ')&.last&.tr('\'', '')&.tr('"', '')&.to_sym + rescue Errno::ENOENT => e + ExceptionNotifier.notify_exception(e, data: { path: path }) + + :post + end + + # Genera un Post nuevo + # + # @todo Mergear en Post#initialize + # @params :path [String] + # @params :site [Site] + # @params :locale [String, Symbol] + # @params :document [Jekyll::Document] + # @params :layout [String,Symbol] + # @return [Post] + def build(**args) + args[:path] ||= '' + args[:document] ||= + begin + site = args[:site] + collection = site.collections[args[:locale].to_s] + + Jekyll::Document.new(args[:path], site: site.jekyll, collection: collection).tap do |doc| + doc.data['date'] = Date.today.to_time if args[:path].blank? + end + end + + args[:layout] = args[:site].layouts[args[:layout]] if args[:layout].is_a? Symbol + + Post.new(**args) end end # Redefinir el inicializador de OpenStruct # - # @param site: [Site] el sitio en Sutty - # @param document: [Jekyll::Document] el documento leído por Jekyll - # @param layout: [Layout] la plantilla - # + # @param :site [Site] el sitio en Sutty + # @param :document [Jekyll::Document] el documento leído por Jekyll + # @param :layout [Layout] la plantilla def initialize(**args) default_attributes_missing(**args) @@ -289,8 +318,6 @@ class Post def destroy run_callbacks :destroy do FileUtils.rm_f path.absolute - - site.delete_post self end end alias destroy! destroy diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index 4e46d7b2..38a98c2b 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -6,9 +6,11 @@ class Post extend ActiveSupport::Concern included do - # Indexa o reindexa el Post - after_save :index! - after_destroy :remove_from_index! + + # @return [IndexedPost,nil] + def indexed_post + site.indexed_posts.find_by_post_id(uuid.value) + end # Devuelve una versión indexable del Post # @@ -40,16 +42,19 @@ class Post private - # Los metadatos que se almacenan como objetos JSON. Empezamos con - # las categorías porque se usan para filtrar en el listado de - # artículos. + # Los metadatos que se almacenan como objetos JSON. # # @return [Hash] def indexable_front_matter {}.tap do |ifm| ifm[:usuaries] = usuaries.map(&:id) ifm[:draft] = attribute?(:draft) ? draft.value : false - ifm[:categories] = categories.indexable_values if attribute? :categories + + indexable_attributes.select do |attr| + self[attr].front_matter? + end.each do |attr| + ifm[attr] = self[attr].try(:indexable_values) + end end end diff --git a/app/models/post_relation.rb b/app/models/post_relation.rb deleted file mode 100644 index 531d3cc4..00000000 --- a/app/models/post_relation.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -# La relación de un sitio con sus artículos, esto nos permite generar -# artículos como si estuviésemos usando ActiveRecord. -class PostRelation < Array - # No necesitamos cambiar el sitio - attr_reader :site, :lang - - def initialize(site:, lang:) - @site = site - @lang = lang - # Proseguimos la inicialización sin valores por defecto - super() - end - - # Genera un artículo nuevo con los parámetros que le pasemos y lo suma - # al array - def build(**args) - args[:lang] = lang - args[:document] ||= build_document(collection: args[:lang]) - args[:layout] = build_layout(args[:layout]) - - post = Post.new(site: site, **args) - - self << post - post - end - - def create(**args) - post = build(**args) - post.save - post - end - - alias sort_by_generic sort_by - alias sort_by_generic! sort_by! - - # Permite ordenar los artículos por sus atributos - # - # XXX: Prestar atención cuando estamos mezclando artículos con - # diferentes tipos de atributos. - def sort_by(*attrs) - sort_by_generic do |post| - attrs.map do |attr| - # TODO: detectar el tipo de atributo faltante y obtener el valor - # por defecto para hacer la comparación - if post.attributes.include? attr - post.public_send(attr).value - else - 0 - end - end - end - end - - def sort_by!(*attrs) - replace sort_by(*attrs) - end - - alias find_generic find - - # Encontrar un post por su UUID - def find(id, uuid: false) - find_generic do |p| - if uuid - p.uuid.value == id - else - p.id == id - end - end - end - - # Encuentra el primer post por el valor de los atributos - # - # @param [Hash] - # @return [Post] - def find_by(**args) - find_generic do |post| - args.map do |attr, value| - post.attribute?(attr) && - post.public_send(attr).value == value - end.all? - end - end - - # Encuentra todos los Post que cumplan las condiciones - # - # TODO: Implementar caché - # - # @param [Hash] Mapa de atributo => valor. Valor puede ser un Array - # de valores - # @return [PostRelation] - def where(**args) - return self if args.empty? - - begin - PostRelation.new(site: site, lang: lang).concat(select do |post| - result = args.map do |attr, value| - next unless post.attribute?(attr) - - attribute = post[attr] - - # TODO: Si el valor del atributo también es un Array deberíamos - # cruzar ambas. - case value - when Array then value.include? attribute.value - else - case attribute.value - when Array then attribute.value.include? value - else attribute.value == value - end - end - end.compact - - # Un Array vacío devuelve true para all? - result.present? && result.all? - end) - end - end - - # Como Array#select devolviendo una relación - # - # @return [PostRelation] - alias array_select select - def select(&block) - PostRelation.new(site: site, lang: lang).concat array_select(&block) - end - - # Intenta guardar todos y devuelve true si pudo - def save_all(validate: true) - map do |post| - post.save(validate: validate) - end.all? - end - - private - - def build_layout(layout = nil) - return layout if layout.is_a? Layout - - site.layouts[layout&.to_sym || :post] - end - - # Devuelve una colección Jekyll que hace pasar el documento - def build_collection(label:) - Jekyll::Collection.new(site.jekyll, label.to_s) - end - - # Un documento borrador con algunas propiedades por defecto - def build_document(collection:) - col = build_collection(label: collection) - doc = Jekyll::Document.new('', site: site.jekyll, collection: col) - doc.data['date'] = Date.today.to_time - doc - end -end diff --git a/app/models/site.rb b/app/models/site.rb index 9c94c31b..9cc42cd0 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -57,8 +57,7 @@ class Site < ApplicationRecord before_create :clone_skel! # Elimina el directorio al destruir un sitio before_destroy :remove_directories! - # Cambiar el nombre del directorio - before_update :update_name! + before_save :add_private_key_if_missing! # Guardar la configuración si hubo cambios after_save :sync_attributes_with_config! @@ -219,15 +218,10 @@ class Site < ApplicationRecord jekyll.data end - # Traer las colecciones. Todos los artículos van a estar dentro de - # colecciones. + # Trae las colecciones desde el sitio, sin leer su contenido + # + # @return [Hash] def collections - unless @read - jekyll.reader.read_collections - - @read = true - end - jekyll.collections end @@ -236,55 +230,6 @@ class Site < ApplicationRecord @config ||= Site::Config.new(self) end - # Los posts en el idioma actual o en uno en particular - # - # @param lang: [String|Symbol] traer los artículos de este idioma - def posts(lang: nil) - # 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 - - # Crea un Struct dinámico con los valores de los locales, si - # llegamos a pasar un idioma que no existe vamos a tener una - # excepción NoMethodError - @posts ||= Struct.new(*locales).new - - return @posts[lang] unless @posts[lang].blank? - - @posts[lang] = PostRelation.new site: self, lang: lang - - # No fallar si no existe colección para este idioma - # XXX: queremos fallar silenciosamente? - (collections[lang.to_s]&.docs || []).each do |doc| - layout = layouts[Post.find_layout(doc.path)] - - @posts[lang].build(document: doc, layout: layout, lang: lang) - rescue TypeError => e - ExceptionNotifier.notify_exception(e, data: { site: name, site_id: id, path: doc.path }) - end - - @posts[lang] - end - - # Todos los Post del sitio para poder buscar en todos. - # - # @return PostRelation - def docs - @docs ||= PostRelation.new(site: self, lang: :docs).push(locales.flat_map do |locale| - posts(lang: locale) - end).flatten! - end - - # Elimina un artículo de la colección - def delete_post(post) - lang = post.lang.value - - collections[lang.to_s].docs.delete(post.document) && - posts(lang: lang).delete(post) - - post - end - # Obtiene todas las plantillas de artículos # # @return [Hash] { post: Layout } @@ -323,24 +268,6 @@ class Site < ApplicationRecord jekyll.reader.read_layouts end - # Trae todos los valores disponibles para un campo - # - # TODO: Traer recursivamente, si el campo contiene Hash - # - # TODO: Mover a PostRelation#pluck - # - # @param attr [Symbol|String] El atributo a buscar - # @return Array - def everything_of(attr, lang: nil) - Rails.cache.fetch("#{cache_key_with_version}/everything_of/#{lang}/#{attr}", expires_in: 1.hour) do - attr = attr.to_sym - - posts(lang: lang).flat_map do |p| - p[attr].value if p.attribute? attr - end.uniq.compact - end - end - # Poner en la cola de compilación def enqueue! update(status: 'enqueued') if waiting? @@ -457,7 +384,6 @@ class Site < ApplicationRecord @incompatible_layouts = nil @jekyll = nil @config = nil - @posts = nil @docs = nil end @@ -493,12 +419,6 @@ class Site < ApplicationRecord FileUtils.rm_rf path end - def update_name! - return unless name_changed? - - FileUtils.mv path_was, path - reload_jekyll! - end # Sincroniza algunos atributos del sitio con su configuración y # guarda los cambios diff --git a/app/models/site/index.rb b/app/models/site/index.rb index cfa4030a..213ef0bb 100644 --- a/app/models/site/index.rb +++ b/app/models/site/index.rb @@ -16,7 +16,12 @@ class Site def index_posts! Site.transaction do - docs.each(&:index!) + jekyll.read + jekyll.documents.each do |doc| + doc.read! + + Post.build(document: doc, site: self, layout: doc['layout'].to_sym).index! + end update(last_indexed_commit: repository.head_commit.oid) end @@ -99,9 +104,10 @@ class Site indexable_posts.select do |delta| MODIFIED_STATUSES.include? delta.status end.each do |delta| - locale, path = locale_and_path_from(delta.new_file[:path]) + locale, _ = locale_and_path_from(delta.new_file[:path]) + full_path = File.join(self.path, delta.new_file[:path]) - posts(lang: locale).find(path).index! + Post.build(path: full_path, site: self, layout: Post.find_layout(full_path), locale: locale).index! end end diff --git a/app/models/site/repository.rb b/app/models/site/repository.rb index c7056eaa..d41e76f5 100644 --- a/app/models/site/repository.rb +++ b/app/models/site/repository.rb @@ -75,13 +75,18 @@ class Site # Forzamos el checkout para mover el HEAD al último commit y # escribir los cambios rugged.checkout 'HEAD', strategy: :force - - git_sh("git", "lfs", "fetch", "origin", default_branch) - # reemplaza los pointers por los archivos correspondientes - git_sh("git", "lfs", "checkout") + commit end + # Trae todos los archivos desde LFS + # + # @return [Boolean] + def git_lfs_checkout + git_sh('git', 'lfs', 'fetch', 'origin', default_branch) + git_sh('git', 'lfs', 'checkout') + end + # El último commit # # @return [Rugged::Commit] @@ -111,10 +116,30 @@ class Site walker.each.to_a end - # Hay commits sin aplicar? - def needs_pull? - fetch - !commits.empty? + # Detecta si hay que hacer un pull o no + # + # @return [Boolean] + def up_to_date? + rugged.merge_analysis(remote_head_commit).include?(:up_to_date) + end + + # Detecta si es posible adelantar la historia local a la remota o + # necesitamos un merge + # + # @return [Boolean] + def fast_forward? + rugged.merge_analysis(remote_head_commit).include?(:fastforward) + end + + # Mueve la historia local a la remota + # + # @see {https://stackoverflow.com/a/27077322} + # @return [nil] + def fast_forward! + rugged.checkout_tree(remote_head_commit) + rugged.references.update(rugged.head.resolve, remote_head_commit.oid) + + nil end # Guarda los cambios en git diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 4631a9a4..6f42bb22 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -3,12 +3,11 @@ # Este servicio se encarga de crear artículos y guardarlos en git, # asignándoselos a une usuarie PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do - # Crea un artículo nuevo + # Crea un artículo nuevo y modificar las asociaciones # # @return Post def create - self.post = site.posts(lang: locale) - .build(layout: layout) + self.post = Post.build(site: site, locale: locale, layout: layout) post.usuaries << usuarie params[:post][:draft] = true if site.invitade? usuarie @@ -16,42 +15,13 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do post.slug.value = p[:slug] if p[:slug].present? end - commit(action: :created, add: update_related_posts) if post.update(post_params) - - update_site_license! - - # Devolver el post aunque no se haya salvado para poder rescatar los - # errores - post - end - - # Crear un post anónimo, con opciones más limitadas. No usamos post. - def create_anonymous - # XXX: Confiamos en el parámetro de idioma porque estamos - # verificándolos en Site#posts - self.post = site.posts(lang: locale) - .build(layout: layout) - # Los artículos anónimos siempre son borradores - params[:draft] = true - - commit(action: :created, add: [post.path.absolute]) if post.update(anon_post_params) - post - end - - def update - post.usuaries << usuarie - params[:post][:draft] = true if site.invitade? usuarie - - # Eliminar ("mover") el archivo si cambió de ubicación. if post.update(post_params) - rm = [] - rm << post.path.value_was if post.path.changed? + added_paths << post.path.value - # Es importante que el artículo se guarde primero y luego los - # relacionados. - commit(action: :updated, add: update_related_posts, rm: rm) + # Recorrer todas las asociaciones y agregarse donde corresponda + update_associations(post) - update_site_license! + commit(action: :created, add: added_paths) end # Devolver el post aunque no se haya salvado para poder rescatar los @@ -59,6 +29,45 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do post end + # Crear un post anónimo, con opciones más limitadas. No usamos post. + # + # @todo Permitir asociaciones? + def create_anonymous + # XXX: Confiamos en el parámetro de idioma porque estamos + # verificándolos en Site#posts + self.post = Post.build(site: site, locale: locale, layout: layouts) + # Los artículos anónimos siempre son borradores + params[:draft] = true + + commit(action: :created, add: [post.path.absolute]) if post.update(anon_post_params) + post + end + + # Al actualizar, modificamos un post pre-existente, todas las + # relaciones anteriores y las relaciones actuales. + def update + post.usuaries << usuarie + params[:post][:draft] = true if site.invitade? usuarie + + if post.update(post_params) + # Eliminar ("mover") el archivo si cambió de ubicación. + rm = [] + rm << post.path.value_was if post.path.changed? + + added_paths << post.path.value + + # Recorrer todas las asociaciones y agregarse donde corresponda + update_associations(post) + + commit(action: :updated, add: added_paths, rm: rm) + end + + # Devolver el post aunque no se haya salvado para poder rescatar los + # errores + post + end + + # @todo Eliminar relaciones def destroy post.destroy! @@ -74,7 +83,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # { uuid => 2, uuid => 1, uuid => 0 } def reorder reorder = params.require(:post).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i) - posts = site.posts(lang: locale).where(uuid: reorder.keys) + posts = site.indexed_posts.where(locale: locale, post_id: reorder.keys).map(&:post) files = posts.map do |post| next unless post.attribute? :order @@ -90,8 +99,11 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do return if files.empty? # TODO: Implementar transacciones! - posts.save_all(validate: false) && - commit(action: :reorder, add: files) + posts.map do |post| + post.save(validate: false) + end + + commit(action: :reorder, add: files) end private @@ -118,36 +130,16 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do end end + # @return [Symbol] def locale params.dig(:post, :lang)&.to_sym || I18n.locale end + # @return [Layout] def layout - params.dig(:post, :layout) || params[:layout] - end - - # Actualiza los artículos relacionados según los métodos que los - # metadatos declaren. - # - # Este método se asegura que todos los artículos se guardan una sola - # vez. - # - # @return [Array] Lista de archivos modificados - def update_related_posts - posts = Set.new - - post.attributes.each do |a| - post[a].related_methods.each do |m| - next unless post[a].respond_to? m - - # La respuesta puede ser una PostRelation también - posts.merge [post[a].public_send(m)].flatten.compact - end - end - - posts.map do |p| - p.path.absolute if p.save(validate: false) - end.compact << post.path.absolute + site.layouts[ + (params.dig(:post, :layout) || params[:layout]).to_sym + ] end # Si les usuaries modifican o crean una licencia, considerarla @@ -157,4 +149,128 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do site.update licencia: Licencia.find_by_icons('custom') end end + + # @return [Set] + def associated_posts_to_save + @associated_posts_to_save ||= Set.new + end + + # @return [Set] + def added_paths + @added_paths ||= Set.new + end + + # Recolectar campos asociados que no estén vacíos + # + # @param [Post] + # @return [Array] + def association_attributes(post) + post.attributes.select do |attribute| + post[attribute].try(:inverse?) + end + end + + # @param :post_ids [Array] + # @return [Association] + def associated_posts(post_ids) + site.indexed_posts.where(post_id: post_ids).map(&:post) + end + + # Modificar las asociaciones en cascada, manteniendo reciprocidad + # y guardando los archivos correspondientes. + # + # HABTM, Locales: si se rompe de un lado se elimina en el otro y lo + # mismo si se agrega. + # + # HasMany: la relación es de uno a muchos. Al quitar uno, se elimina + # la relación inversa. Al agregar uno, se elimina su relación + # anterior en el tercer Post y se actualiza con la nueva. + # + # BelongsTo: la inversa de HasMany. Al cambiarla, se quita de la + # relación anterior y se agrega en la nueva. + # + # @param :post [Post] + # @return [nil] + def update_associations(post) + association_attributes(post).each do |attribute| + metadata = post[attribute] + + next unless metadata.changed? + + inverse_attribute = post[attribute].inverse + value_was = metadata.value_was.dup + value = metadata.value.dup + + case metadata.type + when 'has_and_belongs_to_many', 'locales' + associated_posts(value_was - value).each do |remove_post| + remove_relation_from(remove_post[inverse_attribute], post.uuid.value) + end + + associated_posts(value - value_was).each do |add_post| + add_relation_to(add_post[inverse_attribute], post.uuid.value) + end + when 'has_many' + associated_posts(value_was - value).each do |remove_post| + remove_relation_from(remove_post[inverse_attribute], '') + end + + associated_posts(value - value_was).each do |add_post| + associated_posts(add_post[inverse_attribute].value_was).each do |remove_post| + remove_relation_from(remove_post[attribute], add_post.uuid.value) + end + + add_relation_to(add_post[inverse_attribute], post.uuid.value) + end + when 'belongs_to', 'has_one' + if value_was.present? + associated_posts(value_was).each do |remove_post| + remove_relation_from(remove_post[inverse_attribute], post.uuid.value) + end + end + + associated_posts(value).each do |add_post| + add_relation_to(add_post[inverse_attribute], post.uuid.value) + end + end + end + + associated_posts_to_save.each do |associated_post| + next unless associated_post.save(validate: false) + + added_paths << associated_post.path.value + end + + nil + end + + # @todo por qué no podemos usar nil para deshabilitar un valor? + # @param :metadata [MetadataTemplate] + # @param :value [String] + # @return [nil] + def remove_relation_from(metadata, value) + case metadata.value + when Array then metadata.value.delete(value) + when String then metadata.value = '' + end + + associated_posts_to_save << metadata.post + nil + end + + # @todo El validador ya debería eliminar valores duplicados + # @param :metadata [MetadataTemplate] + # @param :value [String] + # @return [nil] + def add_relation_to(metadata, value) + case metadata.value + when Array + metadata.value << value + metadata.value.uniq! + when String then metadata.value = value + end + + associated_posts_to_save << metadata.post + nil + end end diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 36868c51..a1007e04 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -24,7 +24,9 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do # # TODO: hacer que el repositorio se cree cuando es necesario, para # que no haya estados intermedios. - site.locales = [usuarie.lang] + I18n.available_locales + site.locales = [usuarie.lang] + + add_role_to_deploys! role add_role_to_deploys! role @@ -82,16 +84,28 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do commit_config(action: :tor) end - # Trae cambios desde la rama remota y reindexa los artículos. + # Trae cambios desde la rama remota # # @return [Boolean] def merge - result = site.repository.merge(usuarie) + site.repository.merge(usuarie).present? + end - # TODO: Implementar callbacks - site.try(:index_posts!) if result + def rename(name) + return if name == site.name + moved = false + site.name = name - result.present? + Site.transaction do + raise ActiveRecord::Rollback if File.exists?(site.path) + FileUtils.mv (site.path_was, site.path) + moved = true + ActiveStorage::Blob.where(service_name: site.name_was).update_all(service_name: site.name) + site.save + rescue StandardError + FileUtils.mv (site.path, site.path_was) if moved + raise + end end private @@ -146,7 +160,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do return true if site.licencia.custom? with_all_locales do |locale| - post = site.posts(lang: locale).find_by(layout: 'license') + post = site.indexed_posts(locale: locale).find_by(layout: 'license')&.post change_licencia(post: post) if post end.compact.map(&:valid?).all? diff --git a/app/views/bootstrap/_custom_checkbox_for_field.haml b/app/views/bootstrap/_custom_checkbox_for_field.haml new file mode 100644 index 00000000..cbbc0079 --- /dev/null +++ b/app/views/bootstrap/_custom_checkbox_for_field.haml @@ -0,0 +1,6 @@ +- content = t("activerecord.attributes.#{field.object_name}.#{name}") +- id = "#{field.object_name}_#{name}" +- name = "#{field.object_name}[#{name}]" + += render 'bootstrap/custom_checkbox', id: id, name: name, content: content, required: local_assigns[:required], value: "1" do + = yield diff --git a/app/views/build_stats/index.haml b/app/views/build_stats/index.haml index de04d84d..c6ba4dfc 100644 --- a/app/views/build_stats/index.haml +++ b/app/views/build_stats/index.haml @@ -1,5 +1,5 @@ %main.row - %aside.menu.col-md-3 + %aside.menu.col-12.col-lg-3 = render 'sites/header', site: @site .col %h1= t('.title') diff --git a/app/views/collaborations/collaborate.haml b/app/views/collaborations/collaborate.haml index cc951b0c..bbdc977e 100644 --- a/app/views/collaborations/collaborate.haml +++ b/app/views/collaborations/collaborate.haml @@ -1,10 +1,10 @@ .row.align-items-center.justify-content-center.full-height - .col-md-10.align-self-center + .col-12.col-lg-10.align-self-center - welcome = @site.config.dig('welcome', 'message') || t('.welcome', site: @site.hostname) = sanitize_markdown welcome - .col-md-6.align-self-center + .col-12.col-lg-6.align-self-center -# Copiado y pegado de app/views/devise/registrations/new.haml - resource = resource_name = @invitade = form_for(resource, as: resource_name, diff --git a/app/views/devise/confirmations/new.haml b/app/views/devise/confirmations/new.haml index c934edc5..fed7523a 100644 --- a/app/views/devise/confirmations/new.haml +++ b/app/views/devise/confirmations/new.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-4.align-self-center + .col-12.col-lg-5.align-self-center .sr-only %h2= t('.resend_confirmation_instructions') diff --git a/app/views/devise/invitations/edit.haml b/app/views/devise/invitations/edit.haml index 3d2f8d76..8039d281 100644 --- a/app/views/devise/invitations/edit.haml +++ b/app/views/devise/invitations/edit.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center %h2= t 'devise.invitations.edit.header' = form_for(resource, as: resource_name, diff --git a/app/views/devise/invitations/new.haml b/app/views/devise/invitations/new.haml index b8b097d0..5fb6e0d0 100644 --- a/app/views/devise/invitations/new.haml +++ b/app/views/devise/invitations/new.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center %h2= t 'devise.invitations.new.header' = form_for(resource, as: resource_name, diff --git a/app/views/devise/mailer/invitation_instructions.html.haml b/app/views/devise/mailer/invitation_instructions.html.haml index e87d99d9..d2d44a67 100644 --- a/app/views/devise/mailer/invitation_instructions.html.haml +++ b/app/views/devise/mailer/invitation_instructions.html.haml @@ -21,4 +21,4 @@ - 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 + %p= link_to t('devise.mailer.invitation_instructions.sign_in'), root_url(change_locale_to: @resource.lang) diff --git a/app/views/devise/passwords/edit.haml b/app/views/devise/passwords/edit.haml index cd8ab8ad..3fab3727 100644 --- a/app/views/devise/passwords/edit.haml +++ b/app/views/devise/passwords/edit.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center .sr-only %h2= t('.change_your_password') %p= t('.help') diff --git a/app/views/devise/passwords/new.haml b/app/views/devise/passwords/new.haml index 4bf7c990..3ef1b624 100644 --- a/app/views/devise/passwords/new.haml +++ b/app/views/devise/passwords/new.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center .sr-only %h2= t('.forgot_your_password') %p= t('.help') diff --git a/app/views/devise/registrations/edit.haml b/app/views/devise/registrations/edit.haml index 8bdc55d9..495a6dea 100644 --- a/app/views/devise/registrations/edit.haml +++ b/app/views/devise/registrations/edit.haml @@ -6,7 +6,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-6.align-self-center + .col-12.col-lg-6.align-self-center %h2= t('.title') = form_for(resource, as: resource_name, diff --git a/app/views/devise/registrations/new.haml b/app/views/devise/registrations/new.haml index aabc0487..cc08c630 100644 --- a/app/views/devise/registrations/new.haml +++ b/app/views/devise/registrations/new.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-6.align-self-center + .col-12.col-lg-6.align-self-center %h2= t('.sign_up') %p= t('.help') diff --git a/app/views/devise/sessions/new.haml b/app/views/devise/sessions/new.haml index 03c3974b..cd205e70 100644 --- a/app/views/devise/sessions/new.haml +++ b/app/views/devise/sessions/new.haml @@ -2,7 +2,7 @@ - 'black-bg' .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center .sr-only %h2= t('.sign_in') %p= t('.help') @@ -28,11 +28,8 @@ placeholder: t('login.password') - if devise_mapping.rememberable? .form-group - = f.check_box :remember_me, aria: { describedby: 'remember-for' } - = f.label :remember_me - %small.form-text.text-muted#remember-for - = t('login.remember_me', - remember_for: distance_of_time_in_words(Usuarie.remember_for)) + = render 'bootstrap/custom_checkbox_for_field', field: f, name: :remember_me do + = t('login.remember_me', remember_for: distance_of_time_in_words(Usuarie.remember_for)) .actions = f.submit t('.sign_in'), class: 'btn btn-secondary btn-lg btn-block' diff --git a/app/views/devise/unlocks/new.haml b/app/views/devise/unlocks/new.haml index 34253f44..eb55d707 100644 --- a/app/views/devise/unlocks/new.haml +++ b/app/views/devise/unlocks/new.haml @@ -4,7 +4,7 @@ = render 'devise/shared/error_messages', resource: resource .row.align-items-center.justify-content-center.full-height - .col-md-5.align-self-center + .col-12.col-lg-5.align-self-center .sr-only %h2= t('.resend_unlock_instructions') %p= t('.help') diff --git a/app/views/layouts/_breadcrumb.haml b/app/views/layouts/_breadcrumb.haml index a946243a..7c3897ff 100644 --- a/app/views/layouts/_breadcrumb.haml +++ b/app/views/layouts/_breadcrumb.haml @@ -1,19 +1,20 @@ -%nav.navbar - %a.navbar-brand.d-none.d-sm-block{ href: '/' } +%nav.navbar.flex-md-nowrap.px-0 + %a.navbar-brand.order-0{ href: '/' } = inline_svg_tag 'sutty.svg', class: 'black', aria: true, title: t('svg.sutty.title'), desc: t('svg.sutty.desc') - %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 - %span.line-clamp-1= link_to crumb.name, crumb.url + - if breadcrumbs? + %nav.flex-grow-1.order-2.order-md-1{ 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 + = link_to crumb.name, crumb.url, class: 'line-clamp-1' - if @current_usuarie || current_usuarie - %ul.navbar-nav.flex-row + %ul.navbar-nav.order-1.order-md-2 - if @site&.tienda? %li.nav-item = link_to t('.tienda'), @site.tienda_url, @@ -30,4 +31,5 @@ - params.permit! - I18n.available_locales.each do |locale| - next if locale == I18n.locale - = link_to t("switch_locale.#{locale}"), params.to_h.merge(change_locale_to: locale) + %li.nav-item + = link_to t("switch_locale.#{locale}"), params.to_h.merge(change_locale_to: locale) diff --git a/app/views/layouts/_details.haml b/app/views/layouts/_details.haml index a21f46c1..b1e28f2c 100644 --- a/app/views/layouts/_details.haml +++ b/app/views/layouts/_details.haml @@ -7,10 +7,13 @@ @param :summary_class [String] Clases para el summary - local_assigns[:summary_class] ||= 'h3' +- local_assigns[:closed] ||= '▶'.html_safe; +- local_assigns[:open] ||= '▼'.html_safe; -%details.details.py-2{ id: local_assigns[:id], data: { controller: 'details', action: 'toggle->details#store' } } + +%details.details.py-2{ id: local_assigns[:id], data: { controller: 'details', action: 'toggle->details#store' }, class: local_assigns[:details_class] } %summary.d-flex.flex-row.align-items-center.justify-content-between{ class: local_assigns[:summary_class] } %span= summary - %span.hide-when-open ▶ - %span.show-when-open ▼ + %span.hide-when-open{ class: local_assigns[:open_class] }= local_assigns[:closed] + %span.show-when-open{ class: local_assigns[:closed_class] }= local_assigns[:open] = yield diff --git a/app/views/posts/attribute_ro/_belongs_to.haml b/app/views/posts/attribute_ro/_belongs_to.haml index c7e06be8..7410e921 100644 --- a/app/views/posts/attribute_ro/_belongs_to.haml +++ b/app/views/posts/attribute_ro/_belongs_to.haml @@ -1,6 +1,6 @@ %tr{ id: attribute } %th= post_label_t(attribute, post: post) %td{ dir: dir, lang: locale } - - p = metadata.belongs_to + - p = site.indexed_posts.find_by_post_id(metadata.value) - if p - = link_to p.title.value, site_post_path(site, p.id) + = link_to p.title, site_post_path(site, p.path) diff --git a/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml b/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml index d6b51a7a..29c0816f 100644 --- a/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml +++ b/app/views/posts/attribute_ro/_has_and_belongs_to_many.haml @@ -2,5 +2,5 @@ %th= post_label_t(attribute, post: post) %td %ul{ dir: dir, lang: locale } - - metadata.has_many.each do |p| - %li= link_to p.title.value, site_post_path(site, p.id) + - site.indexed_posts.where(post_id: metadata.value).find_each do |p| + %li= link_to p.title, site_post_path(site, p.path) diff --git a/app/views/posts/attribute_ro/_has_many.haml b/app/views/posts/attribute_ro/_has_many.haml index d6b51a7a..29c0816f 100644 --- a/app/views/posts/attribute_ro/_has_many.haml +++ b/app/views/posts/attribute_ro/_has_many.haml @@ -2,5 +2,5 @@ %th= post_label_t(attribute, post: post) %td %ul{ dir: dir, lang: locale } - - metadata.has_many.each do |p| - %li= link_to p.title.value, site_post_path(site, p.id) + - site.indexed_posts.where(post_id: metadata.value).find_each do |p| + %li= link_to p.title, site_post_path(site, p.path) diff --git a/app/views/posts/attribute_ro/_has_one.haml b/app/views/posts/attribute_ro/_has_one.haml new file mode 100644 index 00000000..fafccd34 --- /dev/null +++ b/app/views/posts/attribute_ro/_has_one.haml @@ -0,0 +1,6 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td{ dir: dir, lang: locale } + - p = site.indexed_posts.find_by_post_id(metadata.value) + - if p + = link_to p.title, site_post_path(site, p.post_id) diff --git a/app/views/posts/attribute_ro/_related_posts.haml b/app/views/posts/attribute_ro/_related_posts.haml index c43b589e..aa4aff2e 100644 --- a/app/views/posts/attribute_ro/_related_posts.haml +++ b/app/views/posts/attribute_ro/_related_posts.haml @@ -2,10 +2,9 @@ %th= post_label_t(attribute, post: post) %td %ul{ dir: dir, lang: locale } - - metadata.value.each do |v| - - p = site.posts(lang: post.lang.value).find(v, uuid: true) + - site.indexed_posts.where(locale: post.lang.value, post_id: metadata.value).find_each do |p| -# XXX: Ignorar todos los posts no encontrados (ej: fueron borrados o el uuid cambió) - next unless p - %li= link_to p.title.value, site_post_path(site, p.id) + %li= link_to p.title, site_post_path(site, p.path) diff --git a/app/views/posts/attributes/_file.haml b/app/views/posts/attributes/_file.haml index 20c27399..0287366c 100644 --- a/app/views/posts/attributes/_file.haml +++ b/app/views/posts/attributes/_file.haml @@ -1,4 +1,5 @@ .form-group{ data: { controller: 'file-preview' } } + = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) - if metadata.static_file - case metadata.static_file.blob.content_type - when %r{\Avideo/} diff --git a/app/views/posts/attributes/_has_one.haml b/app/views/posts/attributes/_has_one.haml new file mode 100644 index 00000000..b0d21f35 --- /dev/null +++ b/app/views/posts/attributes/_has_one.haml @@ -0,0 +1,7 @@ +.form-group + = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) + = select_tag(plain_field_name_for(base, attribute), + options_for_select(metadata.values, metadata.value), + **field_options(attribute, metadata), include_blank: t('.empty')) + = render 'posts/attribute_feedback', + post: post, attribute: attribute, metadata: metadata diff --git a/app/views/posts/attributes/_image.haml b/app/views/posts/attributes/_image.haml index 241a78e8..a0bfebde 100644 --- a/app/views/posts/attributes/_image.haml +++ b/app/views/posts/attributes/_image.haml @@ -1,4 +1,5 @@ .form-group{ data: { controller: 'file-preview' } } + = label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post) - if metadata.static_file = image_tag url_for(metadata.static_file), alt: metadata.value['description'], diff --git a/app/views/posts/attributes/_locales.haml b/app/views/posts/attributes/_locales.haml index 4978f6b4..05592fbd 100644 --- a/app/views/posts/attributes/_locales.haml +++ b/app/views/posts/attributes/_locales.haml @@ -6,7 +6,6 @@ post: post, attribute: attribute, metadata: metadata - site.locales.each do |locale| - - next if post.lang.value == locale - locale_t = t("locales.#{locale}.name", default: locale.to_s.humanize) - value = metadata.value.find do |v| - metadata.values[locale].values.include? v diff --git a/app/views/posts/edit.haml b/app/views/posts/edit.haml index e7e0260d..d03e08c7 100644 --- a/app/views/posts/edit.haml +++ b/app/views/posts/edit.haml @@ -1,9 +1,3 @@ .row.justify-content-center - .col-md-8 - - if policy(@site).edit? - = render 'layouts/details', summary: t('posts.edit.post') do - = render 'posts/form', site: @site, post: @post - = render 'layouts/details', summary: t('posts.edit.moderation_queue') do - = render 'posts/moderation_queue', site: @site, post: @post, moderation_queue: @moderation_queue - - else - = render 'posts/form', site: @site, post: @post + .col-12.col-lg-8 + = render 'posts/form', site: @site, post: @post diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 3de30aa3..14462261 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -6,20 +6,41 @@ - reorder_target = reorder_controller = {} %main.row - %aside.menu.col-md-3 + %aside.menu.col-lg-3 .mb-3 = render 'sites/header', site: @site = render 'sites/status', site: @site = render 'sites/build', site: @site, class: 'btn-block' = render 'sites/moderation_queue', site: @site, class: 'btn-block' - %h3= t('posts.new') - %table.table.table-sm.mb-3 - %tbody - - @site.schema_organization.each do |schema, _| - - schema = @site.layouts[schema] - - next if schema.hidden? - = render 'schemas/row', site: @site, schema: schema, filter: @filter_params + = render 'layouts/details', summary: t('posts.filters.title') do + + %form{method: :get} + .border.border-magenta.p-1 + - @filter_params.each do |param, values| + - next if param == :layout + - [values].flatten.each do |value| + %input{ type: 'hidden', name: values.is_a?(Array) ? "#{param}[]" : param, value: value } + %legend.font-weight-bold.m-0.h6= 'Tipo de contenido' + - @site.schema_organization.each do |key, _| + .custom-control.custom-checkbox + - schema = @site.layouts[key] + = render 'schemas/filter', site: @site, key: key, schema: schema, filter: @filter_params + %button.btn.btn-secondary.mt-3{ type: 'submit' }= t('posts.filters.submit') + = render 'layouts/details', + summary: t('posts.new'), + summary_class: "h4 magenta font-weight-bold m-0 px-2", + details_class: "d-flex border border-magenta justify-content-between align-items-center w-100 mb-3", + open: "+", closed: "+", + open_class: "h1 magenta font-weight-bold m-0", + closed_class: "h1 magenta font-weight-bold m-0" do + %table.table-sm.w-100 + %tbody + - @site.schema_organization.each do |schema, _| + - schema = @site.layouts[schema] + - next if schema.hidden? + = render 'schemas/row', site: @site, schema: schema, filter: @filter_params + - if policy(@site_stat).index? = link_to t('stats.index.title'), site_stats_path(@site), class: 'btn btn-secondary' @@ -46,30 +67,13 @@ %section.col .d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2 - %form{ action: site_posts_path } - - @filter_params.each do |param, value| - - next if param == 'q' - %input{ type: 'hidden', name: param, value: value } - .form-group.flex-grow-0.m-0 - %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 %nav#locales - @site.locales.each do |locale| = link_to @site.data.dig(locale.to_s, 'locale') || locale, site_posts_path(@site, **@filter_params.merge(locale: locale)), class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}" - .pl-2-plus - - @filter_params.each do |param, value| - - if param == 'layout' - - value = @site.layouts[value.to_sym].humanized_name - = link_to site_posts_path(@site, **@filter_params.reject { |k, _| k == param }), - class: 'btn btn-secondary btn-sm', - title: t('posts.remove_filter_help', filter: value), - aria: { labelledby: "help-filter-#{param}" } do - = value - × + - if @posts.empty? %h2= t('posts.empty') - else @@ -78,7 +82,7 @@ %caption.sr-only= t('posts.caption') %thead %tr.sticky-top - %th.border-0{ colspan: '4' } + %th.border-0.p-0.p-md-2-plus{ colspan: '4' } .d-flex.flex-row.justify-content-between %div - if reorder_allowed @@ -90,6 +94,7 @@ %button.btn.btn-secondary{ data: { action: 'reorder#down' } }= t('posts.reorder.down') %button.btn.btn-secondary{ data: { action: 'reorder#top' } }= t('posts.reorder.top') %button.btn.btn-secondary{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') + %input{ type: 'hidden', name: 'post[lang]', value: @locale } - if @site.pagination %div @@ -102,42 +107,48 @@ -# TODO: Solo les usuaries cachean porque tenemos que separar les botones por permisos. - - cache_if @usuarie, [post, I18n.locale] do - - checkbox_id = "checkbox-#{post.post_id}" - %tr{ id: post.post_id, data: reorder_target } - - if reorder_allowed - %td - .custom-control.custom-checkbox - %input.custom-control-input{ id: checkbox_id, type: 'checkbox', autocomplete: 'off', data: { action: 'reorder#select' } } - %label.custom-control-label{ for: checkbox_id } - %span.sr-only= t('posts.reorder.select') - -# Orden más alto es mayor prioridad - = hidden_field 'post[reorder]', post.post_id, - value: size - i, - data: { reorder: true } - %td.w-100{ class: dir } - = link_to site_post_path(@site, post.path) do - %span{ lang: post.locale, dir: dir }= post.title - - if post.front_matter['draft'].present? - %span.badge.badge-primary= I18n.t('posts.attributes.draft.label') - %br - %small - = link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: post.layout)) - - post.front_matter['categories']&.each do |category| - = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do - %span{ lang: post.locale, dir: dir }= category - = '/' unless post.front_matter['categories'].last == category + - begin + - cache_if @usuarie, [post, I18n.locale] do + - checkbox_id = "checkbox-#{post.post_id}" + %tr{ id: post.post_id, data: reorder_target } + - if reorder_allowed + %td + .custom-control.custom-checkbox + %input.custom-control-input{ id: checkbox_id, type: 'checkbox', autocomplete: 'off', data: { action: 'reorder#select' } } + %label.custom-control-label{ for: checkbox_id } + %span.sr-only= t('posts.reorder.select') + -# Orden más alto es mayor prioridad + = hidden_field 'post[reorder]', post.post_id, + value: size - i, + data: { reorder: true } + %td.w-100{ class: dir } + = link_to site_post_path(@site, post.path) do + %span{ lang: post.locale, dir: dir }= post.title + - if post.front_matter['draft'].present? + %span.badge.badge-primary= I18n.t('posts.attributes.draft.label') + %br + %small + = link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: [post.layout])) + - post.front_matter['categories']&.each do |category| + = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do + %span{ lang: post.locale, dir: dir }= category + = '/' unless post.front_matter['categories'].last == category - %td.text-nowrap - = post.created_at.strftime('%F') - %br/ - = post.order - %td.text-nowrap - .d-flex.flex-row.align-items-start - - if @usuarie || policy(post).edit? - = link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary' - - if @usuarie || policy(post).destroy? - = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary', method: :delete, data: { confirm: t('posts.confirm_destroy') } + %td.text-nowrap + = post.created_at.strftime('%F') + %br/ + = post.order + %td.text-nowrap + .d-flex.flex-row.align-items-start + - if @usuarie || policy(post).edit? + = link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary' + - if @usuarie || policy(post).destroy? + = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary', method: :delete, data: { confirm: t('posts.confirm_destroy') } + -# + Rescatar cualquier error en un post, notificarlo e + ignorar su renderización. + - rescue ActionView::Template::Error => e + - ExceptionNotifier.notify_exception(e.cause, data: { site: @site.name, post: @post.path.absolute, usuarie: current_usuarie.id }) #footnotes{ hidden: true } - @filter_params.each do |param, value| diff --git a/app/views/posts/new.haml b/app/views/posts/new.haml index 6ec252fe..d03e08c7 100644 --- a/app/views/posts/new.haml +++ b/app/views/posts/new.haml @@ -1,3 +1,3 @@ .row.justify-content-center - .col-md-8 + .col-12.col-lg-8 = render 'posts/form', site: @site, post: @post diff --git a/app/views/posts/show.haml b/app/views/posts/show.haml index 10fe64e3..87094755 100644 --- a/app/views/posts/show.haml +++ b/app/views/posts/show.haml @@ -1,6 +1,6 @@ - dir = @site.data.dig(params[:locale], 'dir') .row.justify-content-center - .col-md-8 + .col-12.col-lg-8 %article.content.table-responsive-md = link_to t('posts.edit_post'), edit_site_post_path(@site, @post.id), diff --git a/app/views/schemas/_add.haml b/app/views/schemas/_add.haml index 0131a6bb..6aaf51c9 100644 --- a/app/views/schemas/_add.haml +++ b/app/views/schemas/_add.haml @@ -1 +1 @@ -= link_to t('.add'), new_site_post_path(site, layout: schema.value), class: 'btn btn-secondary btn-sm m-0' += link_to t(schema.humanized_name), new_site_post_path(site, layout: schema.value), class: 'stretched-link black text-decoration-none' diff --git a/app/views/schemas/_filter.haml b/app/views/schemas/_filter.haml index c422c5b8..ca02b11a 100644 --- a/app/views/schemas/_filter.haml +++ b/app/views/schemas/_filter.haml @@ -1,4 +1,10 @@ -- if filter[:layout] == schema.name.to_s - = link_to t('.remove'), site_posts_path(site, **filter.merge(layout: nil)), class: 'btn btn-primary btn-sm m-0' -- else - = link_to t('.filter'), site_posts_path(site, **filter.merge(layout: schema.value)), class: 'btn btn-secondary btn-sm m-0' +%div + %input.custom-control-input.magenta{ type: 'checkbox', id: schema, name: "layout[]", class: "", value: schema.name, checked: @filter_params[:layout]&.include?(key.to_s) } + %label.custom-control-label.font-weight-normal{ for: schema }= schema.humanized_name + + +-# XXX: Solo un nivel de recursividad +- unless local_assigns[:parent_schema] + - schema.schemas.each do |s| + - next if s.hidden? + = render 'schemas/filter', schema: s, key: s.name, site: site, filter: filter \ No newline at end of file diff --git a/app/views/schemas/_row.haml b/app/views/schemas/_row.haml index ece07727..e5ff1409 100644 --- a/app/views/schemas/_row.haml +++ b/app/views/schemas/_row.haml @@ -1,12 +1,10 @@ -%tr - %th.w-100{ scope: 'row' } +%tr.border-top.border-magenta + + %th.font-weight-normal.w-100.position-relative{ scope: 'row' } - if local_assigns[:parent_schema] %span.text-muted — - = schema.humanized_name - %td.px-0.text-nowrap - = render 'schemas/add', **local_assigns - = render 'schemas/filter', **local_assigns - + = render 'schemas/add', schema: schema, **local_assigns + -# XXX: Solo un nivel de recursividad - unless local_assigns[:parent_schema] - schema.schemas.each do |s| diff --git a/app/views/sites/_form.haml b/app/views/sites/_form.haml index ec2712bf..f3055bf7 100644 --- a/app/views/sites/_form.haml +++ b/app/views/sites/_form.haml @@ -158,9 +158,10 @@ %h2= t('.deploys.title') %p.lead= t('.help.deploys') - = f.fields_for :deploys do |deploy| - = render "deploys/#{deploy.object.type.underscore}", - deploy: deploy, site: site + - site.deployment_list.each do |deploy| + = f.fields_for :deploys, deploy do |deploy_fields| + = render "deploys/#{deploy.type.underscore}", + deploy: deploy_fields, site: site .form-group = f.submit submit, class: 'btn btn-secondary btn-lg btn-block' diff --git a/app/views/sites/_header.haml b/app/views/sites/_header.haml index c8931041..d4302d34 100644 --- a/app/views/sites/_header.haml +++ b/app/views/sites/_header.haml @@ -1,3 +1,27 @@ .hyphens{ lang: site.default_locale } %h1= site.title %p.lead= site.description + %form.mb-3{ action: site_posts_path } + - @filter_params.each do |param, values| + - next if param == :q + - [values].flatten.each do |value| + %input{ type: 'hidden', name: values.is_a?(Array) ? "#{param}[]" : param, value: value } + .form-group.flex-grow-0.m-0 + %label.h3{for: 'q'}= t('posts.index.search') + .input-group + %input#q.form-control.border.border-magenta.border-right-0{ type: 'search', name: 'q', value: @filter_params[:q] } + .input-group-append + %span.input-group-text.background-white.magenta.border.border-magenta.border-top.border-left-0.border-right.border-bottom + %i.fa.fa-fw.fa-search + %input.sr-only{ type: 'submit' } + - @filter_params.each do |param, values| + - [values].flatten.each do |value| + = link_to site_posts_path(@site, **filter_params_by(@filter_params, param, value)), + class: 'btn btn-secondary btn-sm', + title: t('posts.remove_filter_help', filter: value), + aria: { labelledby: "help-filter-#{param}" } do + - if param == :layout + = @site.layouts[value.to_sym].humanized_name + - else + = value + × diff --git a/app/views/sites/edit.haml b/app/views/sites/edit.haml index 4ae7308d..2013c6f6 100644 --- a/app/views/sites/edit.haml +++ b/app/views/sites/edit.haml @@ -1,5 +1,5 @@ .row.justify-content-center - .col-md-8 + .col-12.col-lg-8 %h1= t('.title', site: @site.name) = render 'form', site: @site, submit: t('.submit') diff --git a/app/views/sites/fetch.haml b/app/views/sites/fetch.haml index 6d670d6f..ab7a8f3b 100644 --- a/app/views/sites/fetch.haml +++ b/app/views/sites/fetch.haml @@ -1,5 +1,5 @@ .row.justify-content-center - .col-md-8#pull + .col-12.col-lg-8#pull %h1= t('.title') %p.lead= sanitize_markdown t('.help.fetch'), tags: %w[em strong a] @@ -10,7 +10,7 @@ - @commits.each do |commit| .row.justify-content-center - .col-md-8{ id: commit.oid } + .col-12.col-lg-8{ id: commit.oid } %h1= commit.summary %p.lead= render 'layouts/time', time: commit.time @@ -25,6 +25,6 @@ - unless @commits.empty? .row.justify-content-center - .col-md-8 + .col-12.col-lg-8 = link_to t('.merge.request'), site_pull_path(@site), method: 'post', class: 'btn btn-secondary btn-lg' diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index 1befa2d0..2cdfb727 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -1,5 +1,5 @@ %main.row - %aside.col-md-3 + %aside.col-12.col-lg-3 %h1= t('.title') %p.lead= t('.help') - if policy(Site).new? @@ -15,7 +15,6 @@ %tbody - @sites.each do |site| - next unless site.jekyll? - %tr %td %h2 diff --git a/app/views/sites/new.haml b/app/views/sites/new.haml index 68c17882..23ee8b40 100644 --- a/app/views/sites/new.haml +++ b/app/views/sites/new.haml @@ -1,5 +1,5 @@ .row.justify-content-center - .col-md-8 + .col-12.col-lg-8 %h1= t('.title') %p.lead= t('.help') diff --git a/app/views/stats/index.haml b/app/views/stats/index.haml index 1c1a31f1..6832b7ab 100644 --- a/app/views/stats/index.haml +++ b/app/views/stats/index.haml @@ -38,7 +38,7 @@ - if @normalized_urls.present? = line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options - .row.mb-5.row-cols-1.row-cols-md-2 + .row.mb-5.row-cols-1.row-cols-lg-2 - @columns.each_pair do |column, values| - next if values.blank? .col.mb-5 diff --git a/app/views/usuaries/index.haml b/app/views/usuaries/index.haml index f972a91f..265af56a 100644 --- a/app/views/usuaries/index.haml +++ b/app/views/usuaries/index.haml @@ -1,5 +1,5 @@ .row.justify-content-center - .col.col-md-8 + .col.col-lg-8 %h1= t('.title') -# Una tabla de usuaries y otra de invitades, con acciones diff --git a/app/views/usuaries/invite.haml b/app/views/usuaries/invite.haml index 2698fb8f..0a9be9c8 100644 --- a/app/views/usuaries/invite.haml +++ b/app/views/usuaries/invite.haml @@ -1,7 +1,7 @@ - invite_as = t("usuaries.invite_as.#{params[:invite_as]}") .row.justify-content-center - .col.col-md-8 + .col.col-lg-8 %h1= t('.title', invite_as: invite_as) = form_with url: site_usuaries_invite_path(@site), local: true do |f| diff --git a/bin/access_logs b/bin/access_logs index cfeeb57a..b13dbc06 100755 --- a/bin/access_logs +++ b/bin/access_logs @@ -3,7 +3,7 @@ set -e # Volcar y eliminar todos los access logs de dos días atrás date="`dateadd today -1d`" -file="/srv/http/_storage/${date}.psql.gz" +file="/srv/_storage/${date}.psql.gz" test -n "${date}" test ! -f "${file}" diff --git a/config/application.rb b/config/application.rb index ed7e5a78..8dbdcbdd 100644 --- a/config/application.rb +++ b/config/application.rb @@ -68,7 +68,6 @@ module Sutty config.active_record.schema_format = :sql config.to_prepare do - # Load application's model / class decorators Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c| Rails.configuration.cache_classes ? require(c) : load(c) end @@ -87,7 +86,7 @@ module Sutty end def nodes - @nodes ||= ENV.fetch('SUTTY_NODES', '').split(',') + @nodes ||= ENV.fetch('SUTTY_NODES', 'anarres.sutty.nl').split(',') end end end diff --git a/config/database.yml b/config/database.yml index cd599a24..41fae09c 100644 --- a/config/database.yml +++ b/config/database.yml @@ -26,8 +26,4 @@ test: user: <%= ENV['USER'] %> production: - adapter: postgresql - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - database: <%= ENV.fetch('DATABASE') { 'sutty' } %> - user: sutty - host: postgresql + url: <%= ENV['DATABASE_URL'] %> diff --git a/config/environments/test.rb b/config/environments/test.rb index 05506587..bf72d234 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -50,6 +50,15 @@ Rails.application.configure do config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + config.middleware.use ExceptionNotification::Rack, + error_grouping: true, + email: { + email_prefix: '', + sender_address: ENV['DEFAULT_FROM'], + exception_recipients: ENV['EXCEPTION_TO'], + normalize_subject: true + } + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr diff --git a/config/locales/en.yml b/config/locales/en.yml index 5e9a2377..79916a79 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -334,10 +334,6 @@ en: title: Synchronize to another Sutty node success: Success! error: Error - deploy_distributed_press: - title: Distributed Web - success: Success! - error: Error help: You can contact us by replying to this e-mail maintenance_mailer: notice: @@ -508,7 +504,7 @@ en: storage network may continue retaining copies of the data indefinitely. - [Learn more](https://sutty.nl/learn-more-about-publish-to-dweb-functionality/) + [Learn more](https://sutty.nl/en/learn-more-about-publish-to-dweb-functionality/) deploy_social_distributed_press: title: 'Publish on the Fediverse' help: | @@ -631,8 +627,8 @@ en: help: Please, look for the invalid fields to fix them help: name: "This will be the host name for your site, ie. **example**.sutty.nl. Choose an expression up to 63 characters. It can contain only lowercase letters, numbers and dashes, **and no spaces**. It can't start or end with a dash, or be entirely composed of numbers." - title: 'The title can be anything you want' - description: 'You site description that appears in search engines. Between 50 and 160 characters.' + title: 'The title can be anything you want.' + description: 'You site description that appears in search engines. Between 10 and 160 characters.' design: 'Select the design for your site. 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 @@ -709,6 +705,9 @@ en: next: Next page empty: "There are no results for those search parameters." caption: Post list + filters: + title: Filters + submit: Submit attribute_ro: file: download: Download file @@ -746,6 +745,12 @@ en: image: label: Image destroy: Remove image + audio: + label: Audio file + logo: + label: Logo + download: + label: Archivo belongs_to: empty: "(Empty)" predefined_value: @@ -766,7 +771,7 @@ en: date: 'date' order: 'Order' content: 'Text' - new: 'Post types' + new: 'Add content' remove_filter_help: 'Remove the filter: %{filter}' categories: 'Everything' index: @@ -923,14 +928,14 @@ en: queries: show: empty: '(empty)' + build_stats: + index: + title: "Publications" schemas: add: add: 'Add' filter: filter: 'Filter' remove: 'Back' - build_stats: - index: - title: "Publications" indexed_posts: deleted: "Deleted indexed post %{path} from %{site} (records: %{records})" diff --git a/config/locales/es.yml b/config/locales/es.yml index a07b3799..87202ce5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -317,10 +317,6 @@ es: title: Fediverso success: ¡Éxito! error: Hubo un error - deploy_reindex: - title: Reindexación - success: ¡Éxito! - error: Hubo un error deploy_localized_domain: title: Dominio según idioma success: ¡Éxito! @@ -329,12 +325,12 @@ es: title: Sincronizar al servidor alternativo success: ¡Éxito! error: Hubo un error - deploy_full_rsync: - title: Sincronizar a otro nodo de Sutty + deploy_reindex: + title: Reindexación success: ¡Éxito! error: Hubo un error - deploy_distributed_press: - title: Web distribuida + deploy_full_rsync: + title: Sincronizar a otro nodo de Sutty success: ¡Éxito! error: Hubo un error help: Por cualquier duda, responde este correo para contactarte con nosotres. @@ -637,7 +633,7 @@ es: help: name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener hasta 63 letras minúsculas, números y guiones, pero **sin espacios**. No puede empezar ni terminar con guión, ni estar compuesto enteramente por números.' title: 'El título de tu sitio puede ser lo que quieras.' - description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.' + description: 'La descripción del sitio, que saldrá en buscadores. Entre 10 y 160 caracteres.' design: 'Elegí el diseño que va a tener tu sitio aquí. De tanto en tanto vamos sumando diseños nuevos.' licencia: 'Todo lo que publicamos posee automáticamente derechos de autore. Esto significa que nadie puede hacer uso de nuestras @@ -717,6 +713,9 @@ es: next: Página siguiente empty: No hay artículos con estos parámetros de búsqueda. caption: Lista de artículos + filters: + title: Filtros + submit: Aplicar attribute_ro: file: download: Descargar archivo @@ -754,6 +753,12 @@ es: image: label: Imagen destroy: 'Eliminar imagen' + logo: + label: Logo + audio: + label: Audio + download: + label: Archivo belongs_to: empty: "(Vacío)" predefined_value: @@ -775,7 +780,7 @@ es: order: 'Posición' content: 'Cuerpo del artículo' categories: 'Todos' - new: 'Tipos de artículos' + new: 'Agregar contenido' remove_filter_help: 'Quitar este filtro: %{filter}' index: search: 'Buscar' @@ -931,14 +936,14 @@ es: queries: show: empty: '(vacío)' + build_stats: + index: + title: "Publicaciones" schemas: add: add: 'Agregar' filter: filter: 'Filtrar' remove: 'Volver' - build_stats: - index: - title: "Publicaciones" indexed_posts: deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})" diff --git a/db/migrate/20210504224343_create_indexed_posts.rb b/db/migrate/20210504224343_create_indexed_posts.rb index 9cf21538..a88db1f3 100644 --- a/db/migrate/20210504224343_create_indexed_posts.rb +++ b/db/migrate/20210504224343_create_indexed_posts.rb @@ -27,7 +27,7 @@ class CreateIndexedPosts < ActiveRecord::Migration[6.1] # Queremos mostrar el título por separado t.string :title, default: '' # También vamos a mostrar las categorías - t.jsonb :front_matter, default: '{}' + t.jsonb :front_matter, default: {} t.string :content, default: '' t.tsvector :indexed_content diff --git a/db/migrate/20231026211607_deprecate_deploy_reindex.rb b/db/migrate/20231026211607_deprecate_deploy_reindex.rb new file mode 100644 index 00000000..945d01b4 --- /dev/null +++ b/db/migrate/20231026211607_deprecate_deploy_reindex.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Ya no es necesario reindexar por la fuerza +class DeprecateDeployReindex < ActiveRecord::Migration[6.1] + def up + Deploy.where(type: 'DeployReindex').destroy_all + end + + def down;end +end diff --git a/db/structure.sql b/db/structure.sql index 21cf04d0..61cbd6f9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9,6 +9,27 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: - +-- + +-- *not* creating schema, since initdb creates it + + +-- +-- Name: dblink; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS dblink WITH SCHEMA public; + + +-- +-- Name: EXTENSION dblink; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION dblink IS 'connect to other PostgreSQL databases from within a database'; + + -- -- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - -- @@ -2600,6 +2621,13 @@ ALTER TABLE ONLY public.active_storage_attachments ADD CONSTRAINT fk_rails_c3b3935057 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); +-- +-- Name: publisher; Type: PUBLICATION; Schema: -; Owner: - +-- + +CREATE PUBLICATION publisher FOR ALL TABLES WITH (publish = 'insert, update, delete, truncate'); + + -- -- PostgreSQL database dump complete -- diff --git a/monit.conf b/monit.conf index 2b7e50a8..78340482 100644 --- a/monit.conf +++ b/monit.conf @@ -15,7 +15,7 @@ check program fediblocks if status != 0 then alert check program access_logs - with path "/srv/http/bin/access_logs" as uid "app" and gid "www-data" + with path "/srv/bin/access_logs" as uid "rails" and gid "www-data" every "0 0 * * *" if status != 0 then alert diff --git a/public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json b/public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json new file mode 100644 index 00000000..0d41cd78 --- /dev/null +++ b/public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e718c91fc95c2200e15b2849cf21fbb7fdfd738dbdac8a5b81340ada0a568d0d +size 11449 diff --git a/public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json b/public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json deleted file mode 100644 index ecd1aee3..00000000 --- a/public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53b13d54381374696503351fd6661242b1e22ea6f2078678bc560dfcfb701c8a -size 10242 diff --git a/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css b/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css new file mode 100644 index 00000000..109c05a1 --- /dev/null +++ b/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c7d185f6b9de802352ba5905ed3becdf8248e231484006e3ed3efabbd15e9b6 +size 236932 diff --git a/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css.gz b/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css.gz new file mode 100644 index 00000000..e1f69469 --- /dev/null +++ b/public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d85bfc90e555d5a4d45424fe6b7c0b55baa560d4eb1db9ec0fe678d8dc126f7d +size 32753 diff --git a/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css b/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css new file mode 100644 index 00000000..710c9a9a --- /dev/null +++ b/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e7171d20a358f5e98166d531672ebdb759f88d9fe13cc91486628be82e8fff0 +size 238993 diff --git a/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css.gz b/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css.gz new file mode 100644 index 00000000..775069b9 --- /dev/null +++ b/public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52ac8b1f629dc7b506caf0434217495ac158ccfdc0f474b341cd1bae80caa2e1 +size 33110 diff --git a/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css b/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css new file mode 100644 index 00000000..1945b981 --- /dev/null +++ b/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84981ad08b14786d825a89f6de709c8951b403f7dd5f5fa88f6a77e16760f284 +size 237180 diff --git a/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css.gz b/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css.gz new file mode 100644 index 00000000..01e72c45 --- /dev/null +++ b/public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3df707f1055c66159e723d71cbfa6d501df539538a27e9fc63e789ff7df11e39 +size 32845 diff --git a/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css b/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css new file mode 100644 index 00000000..f00dc228 --- /dev/null +++ b/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd3d4a89d43caf601fa759a513f49aa0747c60e9150651a04c26d0fd860c0cc9 +size 238892 diff --git a/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css.gz b/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css.gz new file mode 100644 index 00000000..bde68d8f --- /dev/null +++ b/public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:634e5c385d13e91be408691a232d23c0400b81a2ad143d7c0937c1362b0e18e0 +size 33084 diff --git a/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css b/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css new file mode 100644 index 00000000..b0a8fba2 --- /dev/null +++ b/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02bb7f27db4d2a609e3a800df7d7e0c19abdc70d149af012a25958ea618e5988 +size 239097 diff --git a/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css.gz b/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css.gz new file mode 100644 index 00000000..454cd9c4 --- /dev/null +++ b/public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c9b3f35f17366137ffcda8065ee9d33f79774d4fc692ceddccffe7287b9f94a +size 33100 diff --git a/public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz b/public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz index f5afff36..5b4b25ab 100644 --- a/public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz +++ b/public/assets/arrows-alt-v-89a34626be855d3b1c3199cd75f62cf6327678eed1e126f74b7cbc6de3502606.svg.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f690800fb0f0e2ce7b86b2564f2b4521030854c3eb13ccad730911181b906a1e +oid sha256:a9c026060870ad426fdd5f04de97ab73f9e8d3fbcdb5fc389652c2dbb8d58cac size 359 diff --git a/_deploy/.keep b/public/assets/blazer/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js similarity index 100% rename from _deploy/.keep rename to public/assets/blazer/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js diff --git a/public/assets/blazer/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js.gz b/public/assets/blazer/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js.gz new file mode 100644 index 00000000..3ac4f898 --- /dev/null +++ b/public/assets/blazer/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a808f31c5704c32a1b85155b41b68f60e08929ab31352cd811138a2c70cacf3 +size 20 diff --git a/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css b/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css new file mode 100644 index 00000000..3070f7a1 --- /dev/null +++ b/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1c391e4691ea1afd08ba5daff66c790edd4b324af7dae390b616af36c7c2837 +size 134418 diff --git a/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css.gz b/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css.gz new file mode 100644 index 00000000..f73b6993 --- /dev/null +++ b/public/assets/blazer/application-c68598fc5732d248b857f9712a627ca58483ad188d6764320941b74fa71bbcac.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:858413c8cd07dff8ee14d3d8a2dbd98c6bad6a6dfd5b202a0e3bb00972dc9ef3 +size 23333 diff --git a/public/assets/blazer/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz b/public/assets/blazer/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz index 44d30965..79704b9c 100644 --- a/public/assets/blazer/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz +++ b/public/assets/blazer/glyphicons-halflings-regular-0805fb1fe24235f70a639f67514990e4bfb6d2cfb00ca563ad4b553c240ddc33.eot.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:232dfba09dd29cfeff9e075a9ba30ed192f678b5946c1a1ec022e9887cc1dc8f +oid sha256:ec5793d14d9c94d2e77f2402cc72cb02fbad30c2f9e694db24a1770c68b85ef2 size 20056 diff --git a/public/assets/blazer/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz b/public/assets/blazer/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz index 49a5ccf2..ab024c17 100644 --- a/public/assets/blazer/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz +++ b/public/assets/blazer/glyphicons-halflings-regular-22d0c88a49d7d0ebe45627143a601061a32a46a9b9afd2dc7f457436f5f15f6e.svg.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2879447637c85f780c62c9decfdc9b6847dfddc0c353ecf056a5563afd5d98c0 +oid sha256:3c0063a75b931092fa7dc19eab7df95f9b715c4444a164f3d63a37c3a7ac4b8f size 26508 diff --git a/public/assets/blazer/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz b/public/assets/blazer/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz index e77b3e37..5c760dc2 100644 --- a/public/assets/blazer/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz +++ b/public/assets/blazer/glyphicons-halflings-regular-7c9caa5f4e16169b0129fdf93c84e85ad14d6c107eb1b0ad60b542daf01ee1f0.ttf.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0668c774700aef315f25dcf8376be7aa88eec8b609b47f37c9ba53bbdb997ee +oid sha256:54d10fbf4ee64ed8263cac863485e034c3f430afd686301c51cde3ecd49612b9 size 23360 diff --git a/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css b/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css new file mode 100644 index 00000000..ec99e712 --- /dev/null +++ b/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df567b9eefa4ec9a50433a8e572abc260ed033b21bf7befd0ad52822270c28b6 +size 312 diff --git a/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css.gz b/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css.gz new file mode 100644 index 00000000..f384cf5a --- /dev/null +++ b/public/assets/dark-591813c5ed25b766eda449e80715f9e51238c5defc9e38e60f02c31e11207839.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:771de7cf116906bf574d88d9138a236e4725dfdcc8a007d8a02573b3661528ed +size 162 diff --git a/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css b/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css new file mode 100644 index 00000000..4a76781b --- /dev/null +++ b/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:577591626adad685d7bdbaab0ae0590841b222586986876b3dacf157c52dffa3 +size 284 diff --git a/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css.gz b/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css.gz new file mode 100644 index 00000000..74ba6f4f --- /dev/null +++ b/public/assets/dark-eb9f64ef7c7dac0cb3eb2b7bd511327d12625e00d4e11216441787288070f7dd.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eff0565120128861b1f1c651ad7eeef39bc680b6b70bcc85aab4cd7abd5728f +size 156 diff --git a/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css b/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css new file mode 100644 index 00000000..ba01cf6c --- /dev/null +++ b/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fddb41356da3c564c20328f29aac7f13980c386b9d0731601c3adf476764295 +size 432 diff --git a/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css.gz b/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css.gz new file mode 100644 index 00000000..0adccddc --- /dev/null +++ b/public/assets/dark-fb3fa48a29ed7f1bf5a9e28a71946b7a7a725aef3e7e96ad6c1a48133cfba2a4.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2e82d47778ea7497888e28c5c92b22be7aea13c09b93b3a362778106d97a751 +size 189 diff --git a/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css b/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css new file mode 100644 index 00000000..9ae9a755 --- /dev/null +++ b/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e32f62213d4cd9e433d7f04fbabe103ec0e90af209ed204a9fff85984d353c7d +size 332 diff --git a/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css.gz b/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css.gz new file mode 100644 index 00000000..c05fd12e --- /dev/null +++ b/public/assets/dark-ff88c7cf3bbaeac72c6342c94c6a9534da24bd3d91f6ebf86c56a739464f8095.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6f2ded0d74857c3c4887dd04c4ab67f1ce5c053cd9f091bba3f0ef7aa6f878e +size 166 diff --git a/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css b/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css new file mode 100644 index 00000000..9349b0ad --- /dev/null +++ b/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:403aaacf284c969542b423b9563f4cfa907a81cdf7443da106c3307bc22acd85 +size 2257 diff --git a/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css.gz b/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css.gz new file mode 100644 index 00000000..9b6ecad0 --- /dev/null +++ b/public/assets/editor-39df1ce9e0468043c2d1c998a38d45fac02beffa1b09088918b9c433c0750b4f.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:944d9e5d96f79b8acf12b5e4896d6f60bb9f6fbba5cfb200269ba2b263015f93 +size 687 diff --git a/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css b/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css new file mode 100644 index 00000000..1ee2f722 --- /dev/null +++ b/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cd9507ad59b2d60d4fefd2ee1e20b75c263872ce57bf8d62b6a7fd565d89279 +size 785 diff --git a/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css.gz b/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css.gz new file mode 100644 index 00000000..6aa4a074 --- /dev/null +++ b/public/assets/fonts-c51ac4112aa6f7493cea2408ae5778f034eccdd0565697f589654dabb363d79a.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7ef626d9caeb98918792415865ddb3339176fd6f0d64866263b3e69cf1b90cf +size 379 diff --git a/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css b/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css new file mode 100644 index 00000000..65a56f63 --- /dev/null +++ b/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d967abb43a679cd18d84b114ad0616239a67241a8402185d2929e27b02e1a4b3 +size 171 diff --git a/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css.gz b/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css.gz new file mode 100644 index 00000000..07501a46 --- /dev/null +++ b/public/assets/new_editor-9962df2caf2874010146cfd735216677865b7e37bee68c59e997a9aa2198594d.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d29a581f5f246d75366cda62749a4a9166ad9daeb6bfcc168d7bedc9d973dda +size 115 diff --git a/public/assets/sutty-08b30df17da83a32911e3bb4fa0a2c967148a2f98020a63b6c171c17c94bf05d.svg.gz b/public/assets/sutty-08b30df17da83a32911e3bb4fa0a2c967148a2f98020a63b6c171c17c94bf05d.svg.gz index dffee65e..11eacf60 100644 --- a/public/assets/sutty-08b30df17da83a32911e3bb4fa0a2c967148a2f98020a63b6c171c17c94bf05d.svg.gz +++ b/public/assets/sutty-08b30df17da83a32911e3bb4fa0a2c967148a2f98020a63b6c171c17c94bf05d.svg.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19973f783284df5608ec79a4fa46dce362f5626112c218266f04329b01049c43 +oid sha256:b96644799494b8c2117ce68d763c392418dbb1b065ffc3d2c52061d9a046ed7f size 943 diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 00000000..f00037df --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,50 @@ + + +{ + "name": "Sutty", + "short_name": "Sutty", + "start_url": ".", + "display": "standalone", + "background_color": "#fff", + "description": "Sutty crea sitios seguros, veloces y visibles", + "icons": [ + + + { + "src": "assets/images/icon48.png", + "sizes": "48x48", + "type": "image/png" + }, + + { + "src": "assets/images/icon72.png", + "sizes": "72x72", + "type": "image/png" + }, + + { + "src": "assets/images/icon96.png", + "sizes": "96x96", + "type": "image/png" + }, + + { + "src": "assets/images/icon144.png", + "sizes": "144x144", + "type": "image/png" + }, + + { + "src": "assets/images/icon168.png", + "sizes": "168x168", + "type": "image/png" + }, + + { + "src": "assets/images/icon192.png", + "sizes": "192x192", + "type": "image/png" + } + + ] +} diff --git a/public/packs/css/application-1224e21e.css b/public/packs/css/application-1224e21e.css deleted file mode 100644 index 390ac1f2..00000000 --- a/public/packs/css/application-1224e21e.css +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a7ffc74f9219623a13902d9ac806b9e71cfdabc2428e1f6ae4015da56cb7c7d9 -size 49314 diff --git a/public/packs/css/application-1224e21e.css.br b/public/packs/css/application-1224e21e.css.br deleted file mode 100644 index 1f5776e2..00000000 --- a/public/packs/css/application-1224e21e.css.br +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:68809099d5fd771490c8eee922fe59c2be223a7b3ea6bec6889bc26380dcc788 -size 10017 diff --git a/public/packs/css/application-1224e21e.css.gz b/public/packs/css/application-1224e21e.css.gz deleted file mode 100644 index 3784a199..00000000 --- a/public/packs/css/application-1224e21e.css.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8058f9e1c5cfdf8de6d896fe9dd138b527e2409e1c467f79b376f75fa0c24b76 -size 12355 diff --git a/public/packs/css/application-e0d66cef.css b/public/packs/css/application-e0d66cef.css new file mode 100644 index 00000000..cca00ffe --- /dev/null +++ b/public/packs/css/application-e0d66cef.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:185c95d8b69c7b6265a5e7a68ba926c2bd1f768fda8cf3be3368137ef6357730 +size 50273 diff --git a/public/packs/css/application-e0d66cef.css.br b/public/packs/css/application-e0d66cef.css.br new file mode 100644 index 00000000..6e13a79f --- /dev/null +++ b/public/packs/css/application-e0d66cef.css.br @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f5d0b5074f2b7fbbc9883beb27da4b4ca91652d3a0c931896a2fcaf97ec8142 +size 10185 diff --git a/public/packs/css/application-e0d66cef.css.gz b/public/packs/css/application-e0d66cef.css.gz new file mode 100644 index 00000000..aced299a --- /dev/null +++ b/public/packs/css/application-e0d66cef.css.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7df2acc6a691d864ccff15bc7d86c6a19a9f780d92d6fd9d1e6db6ff5e25ec +size 12559 diff --git a/public/packs/js/application-5e567763c88fb352be77.js b/public/packs/js/application-5e567763c88fb352be77.js new file mode 100644 index 00000000..46562262 --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62d1f8867d88ec64d12cee2e86ad4fdd363ffe4a3516c0a5c6d65cd3219624a7 +size 1644364 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.LICENSE.txt b/public/packs/js/application-5e567763c88fb352be77.js.LICENSE.txt new file mode 100644 index 00000000..dfe27ce7 --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.LICENSE.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7073b760337ff91f74933ece915ce12f8653f990f607a0925cc002dd610fa0f9 +size 1097 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.br b/public/packs/js/application-5e567763c88fb352be77.js.br new file mode 100644 index 00000000..ed58903d --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.br @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c91173b409bd6e84f40cefdc19d466c848e94d20087cb23a063d2b7bd54458d1 +size 366685 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.gz b/public/packs/js/application-5e567763c88fb352be77.js.gz new file mode 100644 index 00000000..b187d06b --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:908bd87788fff1388ed76e60646715aaac6c6f65d5e28620d338b1b39e3434f4 +size 483358 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.map b/public/packs/js/application-5e567763c88fb352be77.js.map new file mode 100644 index 00000000..094ae7c1 --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.map @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd0e2c4a400ba5fdb9d795dc0d533814d5180e89aa2ac2ba0954061e47d6f959 +size 6387599 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.map.br b/public/packs/js/application-5e567763c88fb352be77.js.map.br new file mode 100644 index 00000000..20280fd8 --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.map.br @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f4de443176d1e60abc706a51412d3bb450a48a4c4af564de168cdb13dcf55e6 +size 1375691 diff --git a/public/packs/js/application-5e567763c88fb352be77.js.map.gz b/public/packs/js/application-5e567763c88fb352be77.js.map.gz new file mode 100644 index 00000000..6d5abc8e --- /dev/null +++ b/public/packs/js/application-5e567763c88fb352be77.js.map.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8821c193db88c59c72f379ee5c4dbce624dab28802722e623b7bbb4ec12ff7ff +size 1701215 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js b/public/packs/js/application-d4a959210a82d3d1b10f.js deleted file mode 100644 index ae056684..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d178fb353afcf2dedc8bba8ce4b978f0bc93f679479a7f67c0473e25324a72c -size 1516360 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.LICENSE.txt b/public/packs/js/application-d4a959210a82d3d1b10f.js.LICENSE.txt deleted file mode 100644 index 979d1ab9..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.LICENSE.txt +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3b9ae1697c4b8a404afe77afe035de28b7f4880e9f52caac82620bb8d8ed495 -size 854 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.br b/public/packs/js/application-d4a959210a82d3d1b10f.js.br deleted file mode 100644 index b7a543a0..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.br +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f37b681c0c2989dba2d59695f7d3d38c9357edab713b2b5899bf2c20dbed1f11 -size 333228 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.gz b/public/packs/js/application-d4a959210a82d3d1b10f.js.gz deleted file mode 100644 index f800b3fd..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a34e726274558a688517e19a1761f028072b7a6f614b4d1ec6f8609e61443bb4 -size 441095 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.map b/public/packs/js/application-d4a959210a82d3d1b10f.js.map deleted file mode 100644 index 76a8fd29..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.map +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5c9c622b3d7a39cf332a95f1877dc8d5cbec844fa99cb55c75e45dfed5531dd -size 5988200 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.map.br b/public/packs/js/application-d4a959210a82d3d1b10f.js.map.br deleted file mode 100644 index a9d2dce3..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.map.br +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d5242fa25b04407204920fb98a250ea9af7de4d3575ea1dcb79801f2c002fb8f -size 1279231 diff --git a/public/packs/js/application-d4a959210a82d3d1b10f.js.map.gz b/public/packs/js/application-d4a959210a82d3d1b10f.js.map.gz deleted file mode 100644 index ffbbaff0..00000000 --- a/public/packs/js/application-d4a959210a82d3d1b10f.js.map.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d70208139d5de996bc6a01daacf3fc7e07edf975296795cae487e71e2c198e07 -size 1583975 diff --git a/public/packs/manifest.json b/public/packs/manifest.json index d0f77c7e..5b28f687 100644 --- a/public/packs/manifest.json +++ b/public/packs/manifest.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e5e2ddeee2bb351e8f9e0b16d28fcebd7314227abdffa65e02e83755db591d6 +oid sha256:d51c7b68bc509554c0f29a511aa553c42324b94e9c8e5b7977d109cebcae7978 size 1426 diff --git a/public/packs/manifest.json.br b/public/packs/manifest.json.br index 76978873..2090a5e8 100644 --- a/public/packs/manifest.json.br +++ b/public/packs/manifest.json.br @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7c9ab4526ce1ce929b4d0c242dee97cacc9f79fac73948c42a4167494e251e1 -size 321 +oid sha256:1069474766e7b968961892eb355a70602ee18cea6d00e3b803c4f23b385a686a +size 320 diff --git a/public/packs/manifest.json.gz b/public/packs/manifest.json.gz index c691abe7..93f3717a 100644 --- a/public/packs/manifest.json.gz +++ b/public/packs/manifest.json.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:caf56db4d1167dd81eadb2da3a1fb6d14bf9f668381d4aa8295f333bcd649f00 +oid sha256:d6ee7bca4e6e888c05b69c1ad646395ea87fe2861858b824b3df5d15a72b8ee8 size 365 diff --git a/test/controllers/posts_controller_test.rb b/test/controllers/posts_controller_test.rb index b8c9f560..3349f09b 100644 --- a/test/controllers/posts_controller_test.rb +++ b/test/controllers/posts_controller_test.rb @@ -150,7 +150,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest end posts = @site.posts(**lang) - reorder = Hash[posts.map { |p| p.uuid.value }.shuffle.each_with_index.to_a] + reorder = posts.map { |p| p.uuid.value }.shuffle.each_with_index.to_a.to_h post site_posts_reorder_url(@site), headers: @authorization, @@ -159,10 +159,18 @@ class PostsControllerTest < ActionDispatch::IntegrationTest @site = Site.find @site.id assert_equal reorder, - Hash[@site.posts(**lang).map do |p| + @site.posts(**lang).map do |p| [p.uuid.value, p.order.value] - end] + end.to_h assert_equal I18n.t('post_service.reorder'), @site.repository.rugged.head.target.message end + + test 'si hay algún error se recupera' do + File.open(File.join(@site.path, '_es', "#{Date.today}-#{SecureRandom.hex}.markdown"), 'w') do |f| + f.write '' + end + + get site_posts_url(@site), headers: @authorization + end end