mirror of
https://0xacab.org/sutty/sutty
synced 2025-03-15 01:48:17 +00:00
Merge branch 'issue-13586-2' into 'rails'
Implementación UX | Columna izquierda posts index See merge request sutty/sutty!245
This commit is contained in:
commit
a771a29e99
148 changed files with 1027 additions and 837 deletions
|
@ -46,6 +46,7 @@ assets:
|
||||||
- "rails"
|
- "rails"
|
||||||
- "production.panel.sutty.nl"
|
- "production.panel.sutty.nl"
|
||||||
- "panel.sutty.nl"
|
- "panel.sutty.nl"
|
||||||
|
- "panel.testing.sutty.nl"
|
||||||
except:
|
except:
|
||||||
- "schedules"
|
- "schedules"
|
||||||
cache:
|
cache:
|
||||||
|
|
|
@ -22,8 +22,6 @@ RUN apk add npm && npm install -g pnpm@~7 && apk del npm
|
||||||
|
|
||||||
COPY ./monit.conf /etc/monit.d/sutty.conf
|
COPY ./monit.conf /etc/monit.d/sutty.conf
|
||||||
|
|
||||||
RUN apk add npm && npm install -g pnpm && apk del npm
|
|
||||||
|
|
||||||
VOLUME "/srv"
|
VOLUME "/srv"
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -118,9 +118,8 @@ group :development, :test do
|
||||||
gem 'derailed_benchmarks'
|
gem 'derailed_benchmarks'
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
# Adds support for Capybara system testing and selenium driver
|
gem 'capybara'
|
||||||
gem 'capybara', '~> 2.13'
|
gem 'selenium-webdriver'
|
||||||
gem 'selenium-webdriver', '~> 4.8.0'
|
|
||||||
gem 'sqlite3'
|
gem 'sqlite3'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
27
Gemfile.lock
27
Gemfile.lock
|
@ -118,13 +118,15 @@ GEM
|
||||||
bundler-audit (0.9.1)
|
bundler-audit (0.9.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
capybara (2.18.0)
|
capybara (3.40.0)
|
||||||
addressable
|
addressable
|
||||||
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (~> 1.11)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.6.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.6.3)
|
||||||
xpath (>= 2.0, < 4.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
|
xpath (~> 3.2)
|
||||||
chartkick (5.0.2)
|
chartkick (5.0.2)
|
||||||
climate_control (1.2.0)
|
climate_control (1.2.0)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
|
@ -360,13 +362,14 @@ GEM
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.0.2)
|
marcel (1.0.2)
|
||||||
|
matrix (0.4.2)
|
||||||
memory_profiler (1.0.1)
|
memory_profiler (1.0.1)
|
||||||
mercenary (0.4.0)
|
mercenary (0.4.0)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mini_histogram (0.3.1)
|
mini_histogram (0.3.1)
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.5)
|
mini_portile2 (2.8.6)
|
||||||
minitest (5.21.1)
|
minitest (5.21.1)
|
||||||
mobility (1.2.9)
|
mobility (1.2.9)
|
||||||
i18n (>= 0.6.10, < 2)
|
i18n (>= 0.6.10, < 2)
|
||||||
|
@ -386,7 +389,7 @@ GEM
|
||||||
net-ssh (7.2.1)
|
net-ssh (7.2.1)
|
||||||
netaddr (2.0.6)
|
netaddr (2.0.6)
|
||||||
nio4r (2.7.0-x86_64-linux-musl)
|
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)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
@ -407,7 +410,7 @@ GEM
|
||||||
pry (0.14.2)
|
pry (0.14.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
public_suffix (5.0.4)
|
public_suffix (5.0.5)
|
||||||
puma (6.4.2-x86_64-linux-musl)
|
puma (6.4.2-x86_64-linux-musl)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.3.1)
|
pundit (2.3.1)
|
||||||
|
@ -486,7 +489,7 @@ GEM
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.9.2)
|
redis-store (1.9.2)
|
||||||
redis (>= 4, < 6)
|
redis (>= 4, < 6)
|
||||||
regexp_parser (2.9.0)
|
regexp_parser (2.9.2)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.1)
|
responders (3.1.1)
|
||||||
|
@ -542,7 +545,7 @@ GEM
|
||||||
sprockets (> 3.0)
|
sprockets (> 3.0)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
tilt
|
tilt
|
||||||
selenium-webdriver (4.8.6)
|
selenium-webdriver (4.9.1)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
|
@ -632,7 +635,7 @@ DEPENDENCIES
|
||||||
bootstrap (~> 4)
|
bootstrap (~> 4)
|
||||||
brakeman
|
brakeman
|
||||||
bundler-audit
|
bundler-audit
|
||||||
capybara (~> 2.13)
|
capybara
|
||||||
chartkick
|
chartkick
|
||||||
commonmarker
|
commonmarker
|
||||||
concurrent-ruby-ext
|
concurrent-ruby-ext
|
||||||
|
@ -707,7 +710,7 @@ DEPENDENCIES
|
||||||
safe_yaml
|
safe_yaml
|
||||||
safely_block (~> 0.3.0)
|
safely_block (~> 0.3.0)
|
||||||
sassc-rails
|
sassc-rails
|
||||||
selenium-webdriver (~> 4.8.0)
|
selenium-webdriver
|
||||||
sourcemap
|
sourcemap
|
||||||
spring
|
spring
|
||||||
spring-watcher-listen
|
spring-watcher-listen
|
||||||
|
|
11
Procfile
11
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
|
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
|
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
|
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
|
fediblock: bundle exec rails activity_pub:fediblocks
|
||||||
|
|
|
@ -20,6 +20,8 @@ $form-feedback-valid-color: $black;
|
||||||
$form-feedback-invalid-color: $magenta;
|
$form-feedback-invalid-color: $magenta;
|
||||||
$form-feedback-icon-valid-color: $black;
|
$form-feedback-icon-valid-color: $black;
|
||||||
$component-active-bg: $magenta;
|
$component-active-bg: $magenta;
|
||||||
|
$btn-white-space: nowrap;
|
||||||
|
$font-weight-bolder: 700;
|
||||||
|
|
||||||
$spacers: (
|
$spacers: (
|
||||||
2-plus: 0.75rem
|
2-plus: 0.75rem
|
||||||
|
@ -51,7 +53,7 @@ $sizes: (
|
||||||
.editor {
|
.editor {
|
||||||
.editor-content {
|
.editor-content {
|
||||||
figure {
|
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;
|
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
|
// TODO: Encontrar la forma de generar esto desde los locales de Rails
|
||||||
|
@ -135,6 +157,10 @@ a {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 1px solid var(--color);
|
||||||
|
}
|
||||||
|
|
||||||
&[target=_blank] {
|
&[target=_blank] {
|
||||||
/* TODO: Convertir a base64 para no hacer peticiones extra */
|
/* TODO: Convertir a base64 para no hacer peticiones extra */
|
||||||
&:after {
|
&:after {
|
||||||
|
@ -143,6 +169,8 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$footer-height: 60px;
|
$footer-height: 60px;
|
||||||
|
|
||||||
/* Colores */
|
/* Colores */
|
||||||
|
@ -256,6 +284,10 @@ svg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
@extend .badge
|
@extend .badge
|
||||||
}
|
}
|
||||||
|
@ -527,6 +559,7 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
|
||||||
color: var(--#{$color});
|
color: var(--#{$color});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
::-moz-selection,
|
::-moz-selection,
|
||||||
::selection {
|
::selection {
|
||||||
background: var(--#{$color});
|
background: var(--#{$color});
|
||||||
|
@ -551,6 +584,7 @@ $bezier: cubic-bezier(0.75, 0, 0.25, 1);
|
||||||
a {
|
a {
|
||||||
color: var(--#{$color});
|
color: var(--#{$color});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,3 +655,18 @@ $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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,4 +31,17 @@ $cyan: #13fefe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.black {
|
||||||
|
color: $white;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color);
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
color: var(--color);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
color: var(--color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,9 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def site
|
def site
|
||||||
@site ||= find_site
|
@site ||= find_site.tap do |s|
|
||||||
|
s.reindex_changes!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -55,9 +55,11 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
def new
|
def new
|
||||||
authorize Post
|
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
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -155,13 +157,13 @@ class PostsController < ApplicationController
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def filter_params
|
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?
|
v.present?
|
||||||
end.transform_keys(&:to_sym)
|
end.transform_keys(&:to_sym)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post
|
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
|
end
|
||||||
|
|
||||||
# Recuerda el nombre del servicio de subida de archivos
|
# Recuerda el nombre del servicio de subida de archivos
|
||||||
|
|
|
@ -209,7 +209,7 @@ class StatsController < ApplicationController
|
||||||
#
|
#
|
||||||
# @return [Integer]
|
# @return [Integer]
|
||||||
def nodes
|
def nodes
|
||||||
@nodes ||= ENV.fetch('NODES', 1).to_i
|
@nodes ||= Rails.application.nodes.size + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def period
|
def period
|
||||||
|
|
|
@ -19,6 +19,25 @@ module ApplicationHelper
|
||||||
[root, name]
|
[root, name]
|
||||||
end
|
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)
|
def plain_field_name_for(*names)
|
||||||
root, name = field_name_for(*names)
|
root, name = field_name_for(*names)
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,8 @@ class DeployJob < ApplicationJob
|
||||||
t << ([type.to_s] + row.values)
|
t << ([type.to_s] + row.values)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
rescue DeployTimedOutException => e
|
||||||
|
notify_exception e
|
||||||
ensure
|
ensure
|
||||||
if site.present?
|
if site.present?
|
||||||
site.update status: 'waiting'
|
site.update status: 'waiting'
|
||||||
|
|
|
@ -1,18 +1,29 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Permite traer los cambios desde webhooks
|
# Permite traer los cambios desde el repositorio remoto
|
||||||
|
|
||||||
class GitPullJob < ApplicationJob
|
class GitPullJob < ApplicationJob
|
||||||
# @param :site [Site]
|
# @param :site [Site]
|
||||||
# @param :usuarie [Usuarie]
|
# @param :usuarie [Usuarie]
|
||||||
|
# @param :message [String]
|
||||||
# @return [nil]
|
# @return [nil]
|
||||||
def perform(site, usuarie)
|
def perform(site, usuarie, message)
|
||||||
@site = site
|
@site = site
|
||||||
|
|
||||||
return unless site.repository.origin
|
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!
|
site.reindex_changes!
|
||||||
|
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
app/models/concerns/metadata/inverse_concern.rb
Normal file
23
app/models/concerns/metadata/inverse_concern.rb
Normal file
|
@ -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
|
|
@ -23,7 +23,7 @@ class DeployDistributedPress < Deploy
|
||||||
#
|
#
|
||||||
# @param :output [Bool]
|
# @param :output [Bool]
|
||||||
# @return [Bool]
|
# @return [Bool]
|
||||||
def deploy
|
def deploy(output: true)
|
||||||
status = false
|
status = false
|
||||||
log = []
|
log = []
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ class DeployLocal < Deploy
|
||||||
|
|
||||||
def bundle(output: false)
|
def bundle(output: false)
|
||||||
run %(bundle config set --local clean 'true'), output: output
|
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 deployment 'true'), output: output if site.gemfile_lock_path?
|
||||||
run %(bundle config set --local path '#{gems_dir}'), output: output
|
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 without 'test development'), output: output
|
||||||
run %(bundle config set --local cache_all 'false'), output: output
|
run %(bundle config set --local cache_all 'false'), output: output
|
||||||
run %(bundle install), output: output
|
run %(bundle install), output: output
|
||||||
|
|
|
@ -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
|
|
|
@ -81,7 +81,7 @@ class DeployRsync < Deploy
|
||||||
#
|
#
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
def user_host
|
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
|
next unless d.size == 1
|
||||||
|
|
||||||
d.insert(0, nil)
|
d.insert(0, nil)
|
||||||
|
|
|
@ -34,15 +34,66 @@ class IndexedPost < ApplicationRecord
|
||||||
scope :in_category, ->(category) { where("front_matter->'categories' ? :category", category: category.to_s) }
|
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) }
|
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
|
belongs_to :site
|
||||||
|
|
||||||
# Encuentra el post original
|
# La ubicación del Post en el disco
|
||||||
#
|
#
|
||||||
# @return [nil,Post]
|
# @return [String]
|
||||||
def post
|
def full_path
|
||||||
return if post_id.blank?
|
@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
|
end
|
||||||
|
|
||||||
# Convertir locale a direccionario de PG
|
# Convertir locale a direccionario de PG
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
# Almacena el UUID de otro Post y actualiza el valor en el Post
|
# Almacena el UUID de otro Post y actualiza el valor en el Post
|
||||||
# relacionado.
|
# relacionado.
|
||||||
class MetadataBelongsTo < MetadataRelatedPosts
|
class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
|
include Metadata::InverseConcern
|
||||||
|
|
||||||
# TODO: Convertir algunos tipos de valores en módulos para poder
|
# TODO: Convertir algunos tipos de valores en módulos para poder
|
||||||
# implementar varios tipos de campo sin repetir código
|
# implementar varios tipos de campo sin repetir código
|
||||||
#
|
#
|
||||||
|
@ -20,82 +22,12 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
document.data[name.to_s]
|
document.data[name.to_s]
|
||||||
end
|
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
|
def indexable_values
|
||||||
belongs_to&.title&.value
|
posts.find_by_post_uuid(value).try(:title)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def post_exists?
|
|
||||||
return true if sanitize(value).blank?
|
|
||||||
|
|
||||||
sanitize(value).present? && belongs_to.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def sanitize(uuid)
|
def sanitize(uuid)
|
||||||
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,8 +58,13 @@ class MetadataContent < MetadataTemplate
|
||||||
|
|
||||||
uri = URI element['src']
|
uri = URI element['src']
|
||||||
|
|
||||||
# No permitimos recursos externos
|
# No permitimos recursos externos, solo si sabemos cuales son
|
||||||
raise URI::Error unless Rails.application.config.hosts.include?(uri.hostname)
|
# 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
|
element['src'] = convert_src_to_internal_path uri
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Establece una relación de muchos a muchos artículos. Cada campo es un
|
# Establece una relación de muchos a muchos artículos
|
||||||
# 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.
|
|
||||||
class MetadataHasAndBelongsToMany < MetadataHasMany
|
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
|
end
|
||||||
|
|
|
@ -6,55 +6,5 @@
|
||||||
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
|
# Localmente tenemos un Array de UUIDs. Remotamente tenemos una String
|
||||||
# apuntando a un Post, que se mantiene actualizado como el actual.
|
# apuntando a un Post, que se mantiene actualizado como el actual.
|
||||||
class MetadataHasMany < MetadataRelatedPosts
|
class MetadataHasMany < MetadataRelatedPosts
|
||||||
# Todos los Post relacionados
|
include Metadata::InverseConcern
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
3
app/models/metadata_has_one.rb
Normal file
3
app/models/metadata_has_one.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MetadataHasOne < MetadataBelongsTo; end
|
|
@ -6,11 +6,16 @@ class MetadataLocales < MetadataHasAndBelongsToMany
|
||||||
#
|
#
|
||||||
# @return { lang: { title: uuid } }
|
# @return { lang: { title: uuid } }
|
||||||
def values
|
def values
|
||||||
@values ||= site.locales.map do |locale|
|
@values ||= other_locales.to_h do |other_locale|
|
||||||
[locale, posts.where(lang: locale).map do |post|
|
[
|
||||||
[title(post), post.uuid.value]
|
other_locale,
|
||||||
end.to_h]
|
posts.where(locale: other_locale).pluck(:title, :layout, :post_id).to_h do |row|
|
||||||
end.to_h
|
row.tap do |value|
|
||||||
|
value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Siempre hay una relación inversa
|
# Siempre hay una relación inversa
|
||||||
|
@ -33,17 +38,13 @@ class MetadataLocales < MetadataHasAndBelongsToMany
|
||||||
#
|
#
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
def other_locales
|
def other_locales
|
||||||
site.locales.reject do |locale|
|
@other_locales ||= site.locales - [locale]
|
||||||
locale == post.lang.value.to_sym
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene todos los posts de los otros locales con el mismo layout
|
# Obtiene todos los posts de los otros locales con el mismo layout
|
||||||
#
|
#
|
||||||
# @return [PostRelation]
|
# @return [IndexedPost::ActiveRecord_AssociationRelation]
|
||||||
def posts
|
def posts
|
||||||
other_locales.map do |locale|
|
site.indexed_posts(locale: other_locales).where(layout: post.layout.value).where.not(post_id: post.uuid.value)
|
||||||
site.posts(lang: locale).where(layout: post.layout.value)
|
|
||||||
end.reduce(&:concat) || PostRelation.new(site: site, lang: 'any')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,7 @@ class MetadataOrder < MetadataTemplate
|
||||||
# El valor según la posición del post en la relación ordenada por
|
# 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
|
# fecha, a fecha más alta, posición más alta
|
||||||
def default_value
|
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
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
|
|
|
@ -3,14 +3,16 @@
|
||||||
# Devuelve una lista de títulos y UUID de todos los posts del mismo
|
# Devuelve una lista de títulos y UUID de todos los posts del mismo
|
||||||
# idioma que el actual, para usar con input-map.js
|
# idioma que el actual, para usar con input-map.js
|
||||||
class MetadataRelatedPosts < MetadataArray
|
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]
|
# @return [Hash]
|
||||||
def values
|
def values
|
||||||
@values ||= posts.map do |p|
|
@values ||= posts.pluck(:title, :created_at, :layout, :post_id).to_h do |row|
|
||||||
next if p.uuid.value == post.uuid.value
|
row.tap do |value|
|
||||||
|
value[0] = "#{value[0]} #{value.delete_at(1).strftime('%F')} (#{site.layouts[value.delete_at(1)].humanized_name})"
|
||||||
[title(p), p.uuid.value]
|
end
|
||||||
end.compact.to_h
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Las relaciones nunca son privadas
|
# Las relaciones nunca son privadas
|
||||||
|
@ -23,23 +25,23 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
end
|
end
|
||||||
|
|
||||||
def indexable_values
|
def indexable_values
|
||||||
posts.where(uuid: value).map(&:title).map(&:value)
|
posts.where(post_id: value).pluck(:title)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
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
|
def posts
|
||||||
site.posts(lang: lang).where(**filter)
|
site.indexed_posts.where(locale: locale).where.not(post_id: post.uuid.value).where(filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
def title(post)
|
# Encuentra el filtro desde el esquema del atributo
|
||||||
"#{post&.title&.value || post&.slug&.value} #{post&.date&.value.strftime('%F')} (#{post.layout.humanized_name})"
|
#
|
||||||
end
|
# @return [Hash,nil]
|
||||||
|
|
||||||
# Encuentra el filtro
|
|
||||||
def filter
|
def filter
|
||||||
layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys || {}
|
layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize(uuid)
|
def sanitize(uuid)
|
||||||
|
|
|
@ -62,20 +62,28 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
end
|
end
|
||||||
|
|
||||||
# Trae el idioma actual del sitio o del panel
|
# Trae el idioma actual del sitio o del panel
|
||||||
|
#
|
||||||
|
# @deprecated Empezar a usar locale
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def lang
|
def lang
|
||||||
@lang ||= post&.lang&.value || I18n.locale
|
@lang ||= post&.lang&.value || I18n.locale.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# El valor por defecto
|
alias_method :locale, :lang
|
||||||
|
|
||||||
|
# El valor por defecto desde el esquema de datos
|
||||||
|
#
|
||||||
|
# @return [any]
|
||||||
def default_value
|
def default_value
|
||||||
layout.metadata.dig(name, 'default', lang.to_s)
|
layout.metadata.dig(name, 'default', lang)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Valores posibles, busca todos los valores actuales en otros
|
# Valores posibles, busca todos los valores actuales en otros
|
||||||
# artículos del mismo sitio
|
# artículos del mismo sitio
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
def values
|
def values
|
||||||
site.everything_of(name, lang: lang)
|
site.indexed_posts.everything_of(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
||||||
|
|
|
@ -30,15 +30,44 @@ class Post
|
||||||
# a demanda?
|
# a demanda?
|
||||||
def find_layout(path)
|
def find_layout(path)
|
||||||
File.foreach(path).lazy.grep(/^layout: /).take(1).first&.split(' ')&.last&.tr('\'', '')&.tr('"', '')&.to_sym
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
# Redefinir el inicializador de OpenStruct
|
# Redefinir el inicializador de OpenStruct
|
||||||
#
|
#
|
||||||
# @param site: [Site] el sitio en Sutty
|
# @param :site [Site] el sitio en Sutty
|
||||||
# @param document: [Jekyll::Document] el documento leído por Jekyll
|
# @param :document [Jekyll::Document] el documento leído por Jekyll
|
||||||
# @param layout: [Layout] la plantilla
|
# @param :layout [Layout] la plantilla
|
||||||
#
|
|
||||||
def initialize(**args)
|
def initialize(**args)
|
||||||
default_attributes_missing(**args)
|
default_attributes_missing(**args)
|
||||||
|
|
||||||
|
@ -289,8 +318,6 @@ class Post
|
||||||
def destroy
|
def destroy
|
||||||
run_callbacks :destroy do
|
run_callbacks :destroy do
|
||||||
FileUtils.rm_f path.absolute
|
FileUtils.rm_f path.absolute
|
||||||
|
|
||||||
site.delete_post self
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
alias destroy! destroy
|
alias destroy! destroy
|
||||||
|
|
|
@ -6,9 +6,11 @@ class Post
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
# Indexa o reindexa el Post
|
|
||||||
after_save :index!
|
# @return [IndexedPost,nil]
|
||||||
after_destroy :remove_from_index!
|
def indexed_post
|
||||||
|
site.indexed_posts.find_by_post_id(uuid.value)
|
||||||
|
end
|
||||||
|
|
||||||
# Devuelve una versión indexable del Post
|
# Devuelve una versión indexable del Post
|
||||||
#
|
#
|
||||||
|
@ -40,16 +42,19 @@ class Post
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Los metadatos que se almacenan como objetos JSON. Empezamos con
|
# Los metadatos que se almacenan como objetos JSON.
|
||||||
# las categorías porque se usan para filtrar en el listado de
|
|
||||||
# artículos.
|
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def indexable_front_matter
|
def indexable_front_matter
|
||||||
{}.tap do |ifm|
|
{}.tap do |ifm|
|
||||||
ifm[:usuaries] = usuaries.map(&:id)
|
ifm[:usuaries] = usuaries.map(&:id)
|
||||||
ifm[:draft] = attribute?(:draft) ? draft.value : false
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
|
@ -57,8 +57,7 @@ class Site < ApplicationRecord
|
||||||
before_create :clone_skel!
|
before_create :clone_skel!
|
||||||
# Elimina el directorio al destruir un sitio
|
# Elimina el directorio al destruir un sitio
|
||||||
before_destroy :remove_directories!
|
before_destroy :remove_directories!
|
||||||
# Cambiar el nombre del directorio
|
|
||||||
before_update :update_name!
|
|
||||||
before_save :add_private_key_if_missing!
|
before_save :add_private_key_if_missing!
|
||||||
# Guardar la configuración si hubo cambios
|
# Guardar la configuración si hubo cambios
|
||||||
after_save :sync_attributes_with_config!
|
after_save :sync_attributes_with_config!
|
||||||
|
@ -219,15 +218,10 @@ class Site < ApplicationRecord
|
||||||
jekyll.data
|
jekyll.data
|
||||||
end
|
end
|
||||||
|
|
||||||
# Traer las colecciones. Todos los artículos van a estar dentro de
|
# Trae las colecciones desde el sitio, sin leer su contenido
|
||||||
# colecciones.
|
#
|
||||||
|
# @return [Hash]
|
||||||
def collections
|
def collections
|
||||||
unless @read
|
|
||||||
jekyll.reader.read_collections
|
|
||||||
|
|
||||||
@read = true
|
|
||||||
end
|
|
||||||
|
|
||||||
jekyll.collections
|
jekyll.collections
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -236,55 +230,6 @@ class Site < ApplicationRecord
|
||||||
@config ||= Site::Config.new(self)
|
@config ||= Site::Config.new(self)
|
||||||
end
|
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
|
# Obtiene todas las plantillas de artículos
|
||||||
#
|
#
|
||||||
# @return [Hash] { post: Layout }
|
# @return [Hash] { post: Layout }
|
||||||
|
@ -323,24 +268,6 @@ class Site < ApplicationRecord
|
||||||
jekyll.reader.read_layouts
|
jekyll.reader.read_layouts
|
||||||
end
|
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
|
# Poner en la cola de compilación
|
||||||
def enqueue!
|
def enqueue!
|
||||||
update(status: 'enqueued') if waiting?
|
update(status: 'enqueued') if waiting?
|
||||||
|
@ -457,7 +384,6 @@ class Site < ApplicationRecord
|
||||||
@incompatible_layouts = nil
|
@incompatible_layouts = nil
|
||||||
@jekyll = nil
|
@jekyll = nil
|
||||||
@config = nil
|
@config = nil
|
||||||
@posts = nil
|
|
||||||
@docs = nil
|
@docs = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -493,12 +419,6 @@ class Site < ApplicationRecord
|
||||||
FileUtils.rm_rf path
|
FileUtils.rm_rf path
|
||||||
end
|
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
|
# Sincroniza algunos atributos del sitio con su configuración y
|
||||||
# guarda los cambios
|
# guarda los cambios
|
||||||
|
|
|
@ -16,7 +16,12 @@ class Site
|
||||||
|
|
||||||
def index_posts!
|
def index_posts!
|
||||||
Site.transaction do
|
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)
|
update(last_indexed_commit: repository.head_commit.oid)
|
||||||
end
|
end
|
||||||
|
@ -99,9 +104,10 @@ class Site
|
||||||
indexable_posts.select do |delta|
|
indexable_posts.select do |delta|
|
||||||
MODIFIED_STATUSES.include? delta.status
|
MODIFIED_STATUSES.include? delta.status
|
||||||
end.each do |delta|
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,12 +76,17 @@ class Site
|
||||||
# escribir los cambios
|
# escribir los cambios
|
||||||
rugged.checkout 'HEAD', strategy: :force
|
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
|
commit
|
||||||
end
|
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
|
# El último commit
|
||||||
#
|
#
|
||||||
# @return [Rugged::Commit]
|
# @return [Rugged::Commit]
|
||||||
|
@ -111,10 +116,30 @@ class Site
|
||||||
walker.each.to_a
|
walker.each.to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
# Hay commits sin aplicar?
|
# Detecta si hay que hacer un pull o no
|
||||||
def needs_pull?
|
#
|
||||||
fetch
|
# @return [Boolean]
|
||||||
!commits.empty?
|
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
|
end
|
||||||
|
|
||||||
# Guarda los cambios en git
|
# Guarda los cambios en git
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
# Este servicio se encarga de crear artículos y guardarlos en git,
|
# Este servicio se encarga de crear artículos y guardarlos en git,
|
||||||
# asignándoselos a une usuarie
|
# asignándoselos a une usuarie
|
||||||
PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
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
|
# @return Post
|
||||||
def create
|
def create
|
||||||
self.post = site.posts(lang: locale)
|
self.post = Post.build(site: site, locale: locale, layout: layout)
|
||||||
.build(layout: layout)
|
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? usuarie
|
params[:post][:draft] = true if site.invitade? usuarie
|
||||||
|
|
||||||
|
@ -16,9 +15,14 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post.slug.value = p[:slug] if p[:slug].present?
|
post.slug.value = p[:slug] if p[:slug].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
commit(action: :created, add: update_related_posts) if post.update(post_params)
|
if post.update(post_params)
|
||||||
|
added_paths << post.path.value
|
||||||
|
|
||||||
update_site_license!
|
# Recorrer todas las asociaciones y agregarse donde corresponda
|
||||||
|
update_associations(post)
|
||||||
|
|
||||||
|
commit(action: :created, add: added_paths)
|
||||||
|
end
|
||||||
|
|
||||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||||
# errores
|
# errores
|
||||||
|
@ -26,11 +30,12 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Crear un post anónimo, con opciones más limitadas. No usamos post.
|
# Crear un post anónimo, con opciones más limitadas. No usamos post.
|
||||||
|
#
|
||||||
|
# @todo Permitir asociaciones?
|
||||||
def create_anonymous
|
def create_anonymous
|
||||||
# XXX: Confiamos en el parámetro de idioma porque estamos
|
# XXX: Confiamos en el parámetro de idioma porque estamos
|
||||||
# verificándolos en Site#posts
|
# verificándolos en Site#posts
|
||||||
self.post = site.posts(lang: locale)
|
self.post = Post.build(site: site, locale: locale, layout: layouts)
|
||||||
.build(layout: layout)
|
|
||||||
# Los artículos anónimos siempre son borradores
|
# Los artículos anónimos siempre son borradores
|
||||||
params[:draft] = true
|
params[:draft] = true
|
||||||
|
|
||||||
|
@ -38,20 +43,23 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Al actualizar, modificamos un post pre-existente, todas las
|
||||||
|
# relaciones anteriores y las relaciones actuales.
|
||||||
def update
|
def update
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? usuarie
|
params[:post][:draft] = true if site.invitade? usuarie
|
||||||
|
|
||||||
# Eliminar ("mover") el archivo si cambió de ubicación.
|
|
||||||
if post.update(post_params)
|
if post.update(post_params)
|
||||||
|
# Eliminar ("mover") el archivo si cambió de ubicación.
|
||||||
rm = []
|
rm = []
|
||||||
rm << post.path.value_was if post.path.changed?
|
rm << post.path.value_was if post.path.changed?
|
||||||
|
|
||||||
# Es importante que el artículo se guarde primero y luego los
|
added_paths << post.path.value
|
||||||
# relacionados.
|
|
||||||
commit(action: :updated, add: update_related_posts, rm: rm)
|
|
||||||
|
|
||||||
update_site_license!
|
# Recorrer todas las asociaciones y agregarse donde corresponda
|
||||||
|
update_associations(post)
|
||||||
|
|
||||||
|
commit(action: :updated, add: added_paths, rm: rm)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||||
|
@ -59,6 +67,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @todo Eliminar relaciones
|
||||||
def destroy
|
def destroy
|
||||||
post.destroy!
|
post.destroy!
|
||||||
|
|
||||||
|
@ -74,7 +83,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
# { uuid => 2, uuid => 1, uuid => 0 }
|
# { uuid => 2, uuid => 1, uuid => 0 }
|
||||||
def reorder
|
def reorder
|
||||||
reorder = params.require(:post).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i)
|
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|
|
files = posts.map do |post|
|
||||||
next unless post.attribute? :order
|
next unless post.attribute? :order
|
||||||
|
@ -90,7 +99,10 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
return if files.empty?
|
return if files.empty?
|
||||||
|
|
||||||
# TODO: Implementar transacciones!
|
# TODO: Implementar transacciones!
|
||||||
posts.save_all(validate: false) &&
|
posts.map do |post|
|
||||||
|
post.save(validate: false)
|
||||||
|
end
|
||||||
|
|
||||||
commit(action: :reorder, add: files)
|
commit(action: :reorder, add: files)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -118,36 +130,16 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Symbol]
|
||||||
def locale
|
def locale
|
||||||
params.dig(:post, :lang)&.to_sym || I18n.locale
|
params.dig(:post, :lang)&.to_sym || I18n.locale
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Layout]
|
||||||
def layout
|
def layout
|
||||||
params.dig(:post, :layout) || params[:layout]
|
site.layouts[
|
||||||
end
|
(params.dig(:post, :layout) || params[:layout]).to_sym
|
||||||
|
]
|
||||||
# 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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Si les usuaries modifican o crean una licencia, considerarla
|
# 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')
|
site.update licencia: Licencia.find_by_icons('custom')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Set<String>]
|
||||||
|
def associated_posts_to_save
|
||||||
|
@associated_posts_to_save ||= Set.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Set<String>]
|
||||||
|
def added_paths
|
||||||
|
@added_paths ||= Set.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recolectar campos asociados que no estén vacíos
|
||||||
|
#
|
||||||
|
# @param [Post]
|
||||||
|
# @return [Array<Symbol>]
|
||||||
|
def association_attributes(post)
|
||||||
|
post.attributes.select do |attribute|
|
||||||
|
post[attribute].try(:inverse?)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param :post_ids [Array<String>]
|
||||||
|
# @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
|
end
|
||||||
|
|
|
@ -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
|
# TODO: hacer que el repositorio se cree cuando es necesario, para
|
||||||
# que no haya estados intermedios.
|
# 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
|
add_role_to_deploys! role
|
||||||
|
|
||||||
|
@ -82,16 +84,28 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
commit_config(action: :tor)
|
commit_config(action: :tor)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Trae cambios desde la rama remota y reindexa los artículos.
|
# Trae cambios desde la rama remota
|
||||||
#
|
#
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def merge
|
def merge
|
||||||
result = site.repository.merge(usuarie)
|
site.repository.merge(usuarie).present?
|
||||||
|
end
|
||||||
|
|
||||||
# TODO: Implementar callbacks
|
def rename(name)
|
||||||
site.try(:index_posts!) if result
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -146,7 +160,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
return true if site.licencia.custom?
|
return true if site.licencia.custom?
|
||||||
|
|
||||||
with_all_locales do |locale|
|
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
|
change_licencia(post: post) if post
|
||||||
end.compact.map(&:valid?).all?
|
end.compact.map(&:valid?).all?
|
||||||
|
|
6
app/views/bootstrap/_custom_checkbox_for_field.haml
Normal file
6
app/views/bootstrap/_custom_checkbox_for_field.haml
Normal file
|
@ -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
|
|
@ -1,5 +1,5 @@
|
||||||
%main.row
|
%main.row
|
||||||
%aside.menu.col-md-3
|
%aside.menu.col-12.col-lg-3
|
||||||
= render 'sites/header', site: @site
|
= render 'sites/header', site: @site
|
||||||
.col
|
.col
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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',
|
- welcome = @site.config.dig('welcome', 'message') || t('.welcome',
|
||||||
site: @site.hostname)
|
site: @site.hostname)
|
||||||
= sanitize_markdown welcome
|
= 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
|
-# Copiado y pegado de app/views/devise/registrations/new.haml
|
||||||
- resource = resource_name = @invitade
|
- resource = resource_name = @invitade
|
||||||
= form_for(resource, as: resource_name,
|
= form_for(resource, as: resource_name,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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
|
.sr-only
|
||||||
%h2= t('.resend_confirmation_instructions')
|
%h2= t('.resend_confirmation_instructions')
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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'
|
%h2= t 'devise.invitations.edit.header'
|
||||||
= form_for(resource,
|
= form_for(resource,
|
||||||
as: resource_name,
|
as: resource_name,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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'
|
%h2= t 'devise.invitations.new.header'
|
||||||
= form_for(resource,
|
= form_for(resource,
|
||||||
as: resource_name,
|
as: resource_name,
|
||||||
|
|
|
@ -21,4 +21,4 @@
|
||||||
- elsif !@resource.confirmed? && @resource.confirmation_token
|
- elsif !@resource.confirmed? && @resource.confirmation_token
|
||||||
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
|
= confirmation_url(@resource, confirmation_token: @resource.confirmation_token, change_locale_to: @resource.lang)
|
||||||
- else
|
- 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)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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
|
.sr-only
|
||||||
%h2= t('.change_your_password')
|
%h2= t('.change_your_password')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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
|
.sr-only
|
||||||
%h2= t('.forgot_your_password')
|
%h2= t('.forgot_your_password')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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')
|
%h2= t('.title')
|
||||||
= form_for(resource,
|
= form_for(resource,
|
||||||
as: resource_name,
|
as: resource_name,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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')
|
%h2= t('.sign_up')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
- 'black-bg'
|
- 'black-bg'
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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
|
.sr-only
|
||||||
%h2= t('.sign_in')
|
%h2= t('.sign_in')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
@ -28,11 +28,8 @@
|
||||||
placeholder: t('login.password')
|
placeholder: t('login.password')
|
||||||
- if devise_mapping.rememberable?
|
- if devise_mapping.rememberable?
|
||||||
.form-group
|
.form-group
|
||||||
= f.check_box :remember_me, aria: { describedby: 'remember-for' }
|
= render 'bootstrap/custom_checkbox_for_field', field: f, name: :remember_me do
|
||||||
= f.label :remember_me
|
= t('login.remember_me', remember_for: distance_of_time_in_words(Usuarie.remember_for))
|
||||||
%small.form-text.text-muted#remember-for
|
|
||||||
= t('login.remember_me',
|
|
||||||
remember_for: distance_of_time_in_words(Usuarie.remember_for))
|
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('.sign_in'),
|
= f.submit t('.sign_in'),
|
||||||
class: 'btn btn-secondary btn-lg btn-block'
|
class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= render 'devise/shared/error_messages', resource: resource
|
= render 'devise/shared/error_messages', resource: resource
|
||||||
|
|
||||||
.row.align-items-center.justify-content-center.full-height
|
.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
|
.sr-only
|
||||||
%h2= t('.resend_unlock_instructions')
|
%h2= t('.resend_unlock_instructions')
|
||||||
%p= t('.help')
|
%p= t('.help')
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
%nav.navbar
|
%nav.navbar.flex-md-nowrap.px-0
|
||||||
%a.navbar-brand.d-none.d-sm-block{ href: '/' }
|
%a.navbar-brand.order-0{ href: '/' }
|
||||||
= inline_svg_tag 'sutty.svg', class: 'black', aria: true,
|
= inline_svg_tag 'sutty.svg', class: 'black', aria: true,
|
||||||
title: t('svg.sutty.title'), desc: t('svg.sutty.desc')
|
title: t('svg.sutty.title'), desc: t('svg.sutty.desc')
|
||||||
|
|
||||||
%nav{ aria: { label: t('.title') } }
|
- if breadcrumbs?
|
||||||
|
%nav.flex-grow-1.order-2.order-md-1{ aria: { label: t('.title') } }
|
||||||
%ol.breadcrumb.m-0.flex-wrap
|
%ol.breadcrumb.m-0.flex-wrap
|
||||||
- breadcrumb_trail do |crumb|
|
- breadcrumb_trail do |crumb|
|
||||||
%li.breadcrumb-item{ class: crumb.current? ? 'active' : '' }
|
%li.breadcrumb-item{ class: crumb.current? ? 'active' : '' }
|
||||||
- if crumb.current?
|
- if crumb.current?
|
||||||
%span.line-clamp-1{ aria: { current: 'page' } }= crumb.name
|
%span.line-clamp-1{ aria: { current: 'page' } }= crumb.name
|
||||||
- else
|
- else
|
||||||
%span.line-clamp-1= link_to crumb.name, crumb.url
|
= link_to crumb.name, crumb.url, class: 'line-clamp-1'
|
||||||
|
|
||||||
- if @current_usuarie || current_usuarie
|
- if @current_usuarie || current_usuarie
|
||||||
%ul.navbar-nav.flex-row
|
%ul.navbar-nav.order-1.order-md-2
|
||||||
- if @site&.tienda?
|
- if @site&.tienda?
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to t('.tienda'), @site.tienda_url,
|
= link_to t('.tienda'), @site.tienda_url,
|
||||||
|
@ -30,4 +31,5 @@
|
||||||
- params.permit!
|
- params.permit!
|
||||||
- I18n.available_locales.each do |locale|
|
- I18n.available_locales.each do |locale|
|
||||||
- next if locale == I18n.locale
|
- next if locale == I18n.locale
|
||||||
|
%li.nav-item
|
||||||
= link_to t("switch_locale.#{locale}"), params.to_h.merge(change_locale_to: locale)
|
= link_to t("switch_locale.#{locale}"), params.to_h.merge(change_locale_to: locale)
|
||||||
|
|
|
@ -7,10 +7,13 @@
|
||||||
@param :summary_class [String] Clases para el summary
|
@param :summary_class [String] Clases para el summary
|
||||||
|
|
||||||
- local_assigns[:summary_class] ||= 'h3'
|
- 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] }
|
%summary.d-flex.flex-row.align-items-center.justify-content-between{ class: local_assigns[:summary_class] }
|
||||||
%span= summary
|
%span= summary
|
||||||
%span.hide-when-open ▶
|
%span.hide-when-open{ class: local_assigns[:open_class] }= local_assigns[:closed]
|
||||||
%span.show-when-open ▼
|
%span.show-when-open{ class: local_assigns[:closed_class] }= local_assigns[:open]
|
||||||
= yield
|
= yield
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
%tr{ id: attribute }
|
%tr{ id: attribute }
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td{ dir: dir, lang: locale }
|
%td{ dir: dir, lang: locale }
|
||||||
- p = metadata.belongs_to
|
- p = site.indexed_posts.find_by_post_id(metadata.value)
|
||||||
- if p
|
- if p
|
||||||
= link_to p.title.value, site_post_path(site, p.id)
|
= link_to p.title, site_post_path(site, p.path)
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td
|
%td
|
||||||
%ul{ dir: dir, lang: locale }
|
%ul{ dir: dir, lang: locale }
|
||||||
- metadata.has_many.each do |p|
|
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
%li= link_to p.title, site_post_path(site, p.path)
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td
|
%td
|
||||||
%ul{ dir: dir, lang: locale }
|
%ul{ dir: dir, lang: locale }
|
||||||
- metadata.has_many.each do |p|
|
- site.indexed_posts.where(post_id: metadata.value).find_each do |p|
|
||||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
%li= link_to p.title, site_post_path(site, p.path)
|
||||||
|
|
6
app/views/posts/attribute_ro/_has_one.haml
Normal file
6
app/views/posts/attribute_ro/_has_one.haml
Normal file
|
@ -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)
|
|
@ -2,10 +2,9 @@
|
||||||
%th= post_label_t(attribute, post: post)
|
%th= post_label_t(attribute, post: post)
|
||||||
%td
|
%td
|
||||||
%ul{ dir: dir, lang: locale }
|
%ul{ dir: dir, lang: locale }
|
||||||
- metadata.value.each do |v|
|
- site.indexed_posts.where(locale: post.lang.value, post_id: metadata.value).find_each do |p|
|
||||||
- p = site.posts(lang: post.lang.value).find(v, uuid: true)
|
|
||||||
-#
|
-#
|
||||||
XXX: Ignorar todos los posts no encontrados (ej: fueron
|
XXX: Ignorar todos los posts no encontrados (ej: fueron
|
||||||
borrados o el uuid cambió)
|
borrados o el uuid cambió)
|
||||||
- next unless p
|
- 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)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
.form-group{ data: { controller: 'file-preview' } }
|
.form-group{ data: { controller: 'file-preview' } }
|
||||||
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
- if metadata.static_file
|
- if metadata.static_file
|
||||||
- case metadata.static_file.blob.content_type
|
- case metadata.static_file.blob.content_type
|
||||||
- when %r{\Avideo/}
|
- when %r{\Avideo/}
|
||||||
|
|
7
app/views/posts/attributes/_has_one.haml
Normal file
7
app/views/posts/attributes/_has_one.haml
Normal file
|
@ -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
|
|
@ -1,4 +1,5 @@
|
||||||
.form-group{ data: { controller: 'file-preview' } }
|
.form-group{ data: { controller: 'file-preview' } }
|
||||||
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
- if metadata.static_file
|
- if metadata.static_file
|
||||||
= image_tag url_for(metadata.static_file),
|
= image_tag url_for(metadata.static_file),
|
||||||
alt: metadata.value['description'],
|
alt: metadata.value['description'],
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
post: post, attribute: attribute, metadata: metadata
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
||||||
- site.locales.each do |locale|
|
- site.locales.each do |locale|
|
||||||
- next if post.lang.value == locale
|
|
||||||
- locale_t = t("locales.#{locale}.name", default: locale.to_s.humanize)
|
- locale_t = t("locales.#{locale}.name", default: locale.to_s.humanize)
|
||||||
- value = metadata.value.find do |v|
|
- value = metadata.value.find do |v|
|
||||||
- metadata.values[locale].values.include? v
|
- metadata.values[locale].values.include? v
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-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
|
= render 'posts/form', site: @site, post: @post
|
||||||
|
|
|
@ -6,21 +6,42 @@
|
||||||
- reorder_target = reorder_controller = {}
|
- reorder_target = reorder_controller = {}
|
||||||
|
|
||||||
%main.row
|
%main.row
|
||||||
%aside.menu.col-md-3
|
%aside.menu.col-lg-3
|
||||||
.mb-3
|
.mb-3
|
||||||
= render 'sites/header', site: @site
|
= render 'sites/header', site: @site
|
||||||
= render 'sites/status', site: @site
|
= render 'sites/status', site: @site
|
||||||
= render 'sites/build', site: @site, class: 'btn-block'
|
= render 'sites/build', site: @site, class: 'btn-block'
|
||||||
= render 'sites/moderation_queue', site: @site, class: 'btn-block'
|
= render 'sites/moderation_queue', site: @site, class: 'btn-block'
|
||||||
|
|
||||||
%h3= t('posts.new')
|
= render 'layouts/details', summary: t('posts.filters.title') do
|
||||||
%table.table.table-sm.mb-3
|
|
||||||
|
%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
|
%tbody
|
||||||
- @site.schema_organization.each do |schema, _|
|
- @site.schema_organization.each do |schema, _|
|
||||||
- schema = @site.layouts[schema]
|
- schema = @site.layouts[schema]
|
||||||
- next if schema.hidden?
|
- next if schema.hidden?
|
||||||
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params
|
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params
|
||||||
|
|
||||||
|
|
||||||
- if policy(@site_stat).index?
|
- if policy(@site_stat).index?
|
||||||
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn btn-secondary'
|
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn btn-secondary'
|
||||||
|
|
||||||
|
@ -46,30 +67,13 @@
|
||||||
|
|
||||||
%section.col
|
%section.col
|
||||||
.d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2
|
.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
|
- if @site.locales.size > 1
|
||||||
%nav#locales
|
%nav#locales
|
||||||
- @site.locales.each do |locale|
|
- @site.locales.each do |locale|
|
||||||
= link_to @site.data.dig(locale.to_s, 'locale') || locale, site_posts_path(@site, **@filter_params.merge(locale: 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' : ''}"
|
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?
|
- if @posts.empty?
|
||||||
%h2= t('posts.empty')
|
%h2= t('posts.empty')
|
||||||
- else
|
- else
|
||||||
|
@ -78,7 +82,7 @@
|
||||||
%caption.sr-only= t('posts.caption')
|
%caption.sr-only= t('posts.caption')
|
||||||
%thead
|
%thead
|
||||||
%tr.sticky-top
|
%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
|
.d-flex.flex-row.justify-content-between
|
||||||
%div
|
%div
|
||||||
- if reorder_allowed
|
- 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#down' } }= t('posts.reorder.down')
|
||||||
%button.btn.btn-secondary{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
%button.btn.btn-secondary{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||||
%button.btn.btn-secondary{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
%button.btn.btn-secondary{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
||||||
|
%input{ type: 'hidden', name: 'post[lang]', value: @locale }
|
||||||
|
|
||||||
- if @site.pagination
|
- if @site.pagination
|
||||||
%div
|
%div
|
||||||
|
@ -102,6 +107,7 @@
|
||||||
-#
|
-#
|
||||||
TODO: Solo les usuaries cachean porque tenemos que separar
|
TODO: Solo les usuaries cachean porque tenemos que separar
|
||||||
les botones por permisos.
|
les botones por permisos.
|
||||||
|
- begin
|
||||||
- cache_if @usuarie, [post, I18n.locale] do
|
- cache_if @usuarie, [post, I18n.locale] do
|
||||||
- checkbox_id = "checkbox-#{post.post_id}"
|
- checkbox_id = "checkbox-#{post.post_id}"
|
||||||
%tr{ id: post.post_id, data: reorder_target }
|
%tr{ id: post.post_id, data: reorder_target }
|
||||||
|
@ -122,7 +128,7 @@
|
||||||
%span.badge.badge-primary= I18n.t('posts.attributes.draft.label')
|
%span.badge.badge-primary= I18n.t('posts.attributes.draft.label')
|
||||||
%br
|
%br
|
||||||
%small
|
%small
|
||||||
= link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: post.layout))
|
= 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|
|
- post.front_matter['categories']&.each do |category|
|
||||||
= link_to site_posts_path(@site, **@filter_params.merge(category: category)) do
|
= link_to site_posts_path(@site, **@filter_params.merge(category: category)) do
|
||||||
%span{ lang: post.locale, dir: dir }= category
|
%span{ lang: post.locale, dir: dir }= category
|
||||||
|
@ -138,6 +144,11 @@
|
||||||
= link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary'
|
= link_to t('posts.edit_post'), edit_site_post_path(@site, post.path), class: 'btn btn-secondary'
|
||||||
- if @usuarie || policy(post).destroy?
|
- 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') }
|
= 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 }
|
#footnotes{ hidden: true }
|
||||||
- @filter_params.each do |param, value|
|
- @filter_params.each do |param, value|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-8
|
||||||
= render 'posts/form', site: @site, post: @post
|
= render 'posts/form', site: @site, post: @post
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- dir = @site.data.dig(params[:locale], 'dir')
|
- dir = @site.data.dig(params[:locale], 'dir')
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-8
|
||||||
%article.content.table-responsive-md
|
%article.content.table-responsive-md
|
||||||
= link_to t('posts.edit_post'),
|
= link_to t('posts.edit_post'),
|
||||||
edit_site_post_path(@site, @post.id),
|
edit_site_post_path(@site, @post.id),
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
- if filter[:layout] == schema.name.to_s
|
%div
|
||||||
= link_to t('.remove'), site_posts_path(site, **filter.merge(layout: nil)), class: 'btn btn-primary btn-sm m-0'
|
%input.custom-control-input.magenta{ type: 'checkbox', id: schema, name: "layout[]", class: "", value: schema.name, checked: @filter_params[:layout]&.include?(key.to_s) }
|
||||||
- else
|
%label.custom-control-label.font-weight-normal{ for: schema }= schema.humanized_name
|
||||||
= link_to t('.filter'), site_posts_path(site, **filter.merge(layout: schema.value)), class: 'btn btn-secondary btn-sm m-0'
|
|
||||||
|
|
||||||
|
-# 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
|
|
@ -1,11 +1,9 @@
|
||||||
%tr
|
%tr.border-top.border-magenta
|
||||||
%th.w-100{ scope: 'row' }
|
|
||||||
|
%th.font-weight-normal.w-100.position-relative{ scope: 'row' }
|
||||||
- if local_assigns[:parent_schema]
|
- if local_assigns[:parent_schema]
|
||||||
%span.text-muted —
|
%span.text-muted —
|
||||||
= schema.humanized_name
|
= render 'schemas/add', schema: schema, **local_assigns
|
||||||
%td.px-0.text-nowrap
|
|
||||||
= render 'schemas/add', **local_assigns
|
|
||||||
= render 'schemas/filter', **local_assigns
|
|
||||||
|
|
||||||
-# XXX: Solo un nivel de recursividad
|
-# XXX: Solo un nivel de recursividad
|
||||||
- unless local_assigns[:parent_schema]
|
- unless local_assigns[:parent_schema]
|
||||||
|
|
|
@ -158,9 +158,10 @@
|
||||||
%h2= t('.deploys.title')
|
%h2= t('.deploys.title')
|
||||||
%p.lead= t('.help.deploys')
|
%p.lead= t('.help.deploys')
|
||||||
|
|
||||||
= f.fields_for :deploys do |deploy|
|
- site.deployment_list.each do |deploy|
|
||||||
= render "deploys/#{deploy.object.type.underscore}",
|
= f.fields_for :deploys, deploy do |deploy_fields|
|
||||||
deploy: deploy, site: site
|
= render "deploys/#{deploy.type.underscore}",
|
||||||
|
deploy: deploy_fields, site: site
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
= f.submit submit, class: 'btn btn-secondary btn-lg btn-block'
|
= f.submit submit, class: 'btn btn-secondary btn-lg btn-block'
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
.hyphens{ lang: site.default_locale }
|
.hyphens{ lang: site.default_locale }
|
||||||
%h1= site.title
|
%h1= site.title
|
||||||
%p.lead= site.description
|
%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
|
||||||
|
×
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-8
|
||||||
%h1= t('.title', site: @site.name)
|
%h1= t('.title', site: @site.name)
|
||||||
|
|
||||||
= render 'form', site: @site, submit: t('.submit')
|
= render 'form', site: @site, submit: t('.submit')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8#pull
|
.col-12.col-lg-8#pull
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
%p.lead= sanitize_markdown t('.help.fetch'), tags: %w[em strong a]
|
%p.lead= sanitize_markdown t('.help.fetch'), tags: %w[em strong a]
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
- @commits.each do |commit|
|
- @commits.each do |commit|
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8{ id: commit.oid }
|
.col-12.col-lg-8{ id: commit.oid }
|
||||||
%h1= commit.summary
|
%h1= commit.summary
|
||||||
%p.lead= render 'layouts/time', time: commit.time
|
%p.lead= render 'layouts/time', time: commit.time
|
||||||
|
|
||||||
|
@ -25,6 +25,6 @@
|
||||||
|
|
||||||
- unless @commits.empty?
|
- unless @commits.empty?
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-8
|
||||||
= link_to t('.merge.request'), site_pull_path(@site),
|
= link_to t('.merge.request'), site_pull_path(@site),
|
||||||
method: 'post', class: 'btn btn-secondary btn-lg'
|
method: 'post', class: 'btn btn-secondary btn-lg'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%main.row
|
%main.row
|
||||||
%aside.col-md-3
|
%aside.col-12.col-lg-3
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
%p.lead= t('.help')
|
%p.lead= t('.help')
|
||||||
- if policy(Site).new?
|
- if policy(Site).new?
|
||||||
|
@ -15,7 +15,6 @@
|
||||||
%tbody
|
%tbody
|
||||||
- @sites.each do |site|
|
- @sites.each do |site|
|
||||||
- next unless site.jekyll?
|
- next unless site.jekyll?
|
||||||
|
|
||||||
%tr
|
%tr
|
||||||
%td
|
%td
|
||||||
%h2
|
%h2
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col-md-8
|
.col-12.col-lg-8
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
%p.lead= t('.help')
|
%p.lead= t('.help')
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
- if @normalized_urls.present?
|
- if @normalized_urls.present?
|
||||||
= line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options
|
= 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|
|
- @columns.each_pair do |column, values|
|
||||||
- next if values.blank?
|
- next if values.blank?
|
||||||
.col.mb-5
|
.col.mb-5
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col.col-md-8
|
.col.col-lg-8
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
|
|
||||||
-# Una tabla de usuaries y otra de invitades, con acciones
|
-# Una tabla de usuaries y otra de invitades, con acciones
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- invite_as = t("usuaries.invite_as.#{params[:invite_as]}")
|
- invite_as = t("usuaries.invite_as.#{params[:invite_as]}")
|
||||||
|
|
||||||
.row.justify-content-center
|
.row.justify-content-center
|
||||||
.col.col-md-8
|
.col.col-lg-8
|
||||||
%h1= t('.title', invite_as: invite_as)
|
%h1= t('.title', invite_as: invite_as)
|
||||||
|
|
||||||
= form_with url: site_usuaries_invite_path(@site), local: true do |f|
|
= form_with url: site_usuaries_invite_path(@site), local: true do |f|
|
||||||
|
|
|
@ -3,7 +3,7 @@ set -e
|
||||||
|
|
||||||
# Volcar y eliminar todos los access logs de dos días atrás
|
# Volcar y eliminar todos los access logs de dos días atrás
|
||||||
date="`dateadd today -1d`"
|
date="`dateadd today -1d`"
|
||||||
file="/srv/http/_storage/${date}.psql.gz"
|
file="/srv/_storage/${date}.psql.gz"
|
||||||
test -n "${date}"
|
test -n "${date}"
|
||||||
test ! -f "${file}"
|
test ! -f "${file}"
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ module Sutty
|
||||||
config.active_record.schema_format = :sql
|
config.active_record.schema_format = :sql
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
# Load application's model / class decorators
|
|
||||||
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c|
|
||||||
Rails.configuration.cache_classes ? require(c) : load(c)
|
Rails.configuration.cache_classes ? require(c) : load(c)
|
||||||
end
|
end
|
||||||
|
@ -87,7 +86,7 @@ module Sutty
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodes
|
def nodes
|
||||||
@nodes ||= ENV.fetch('SUTTY_NODES', '').split(',')
|
@nodes ||= ENV.fetch('SUTTY_NODES', 'anarres.sutty.nl').split(',')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,8 +26,4 @@ test:
|
||||||
user: <%= ENV['USER'] %>
|
user: <%= ENV['USER'] %>
|
||||||
|
|
||||||
production:
|
production:
|
||||||
adapter: postgresql
|
url: <%= ENV['DATABASE_URL'] %>
|
||||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
||||||
database: <%= ENV.fetch('DATABASE') { 'sutty' } %>
|
|
||||||
user: sutty
|
|
||||||
host: postgresql
|
|
||||||
|
|
|
@ -50,6 +50,15 @@ Rails.application.configure do
|
||||||
config.action_mailer.default_url_options = { host: 'localhost',
|
config.action_mailer.default_url_options = { host: 'localhost',
|
||||||
port: 3000 }
|
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.
|
# Print deprecation notices to the stderr.
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
|
|
||||||
|
|
|
@ -334,10 +334,6 @@ en:
|
||||||
title: Synchronize to another Sutty node
|
title: Synchronize to another Sutty node
|
||||||
success: Success!
|
success: Success!
|
||||||
error: Error
|
error: Error
|
||||||
deploy_distributed_press:
|
|
||||||
title: Distributed Web
|
|
||||||
success: Success!
|
|
||||||
error: Error
|
|
||||||
help: You can contact us by replying to this e-mail
|
help: You can contact us by replying to this e-mail
|
||||||
maintenance_mailer:
|
maintenance_mailer:
|
||||||
notice:
|
notice:
|
||||||
|
@ -508,7 +504,7 @@ en:
|
||||||
storage network may continue retaining copies of the data
|
storage network may continue retaining copies of the data
|
||||||
indefinitely.
|
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:
|
deploy_social_distributed_press:
|
||||||
title: 'Publish on the Fediverse'
|
title: 'Publish on the Fediverse'
|
||||||
help: |
|
help: |
|
||||||
|
@ -631,8 +627,8 @@ en:
|
||||||
help: Please, look for the invalid fields to fix them
|
help: Please, look for the invalid fields to fix them
|
||||||
help:
|
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."
|
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'
|
title: 'The title can be anything you want.'
|
||||||
description: 'You site description that appears in search engines. Between 50 and 160 characters.'
|
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!'
|
design: 'Select the design for your site. We add more designs from time to time!'
|
||||||
licencia: 'Everything we publish has automatic copyright. This
|
licencia: 'Everything we publish has automatic copyright. This
|
||||||
means nobody can use our works without explicit permission. By
|
means nobody can use our works without explicit permission. By
|
||||||
|
@ -709,6 +705,9 @@ en:
|
||||||
next: Next page
|
next: Next page
|
||||||
empty: "There are no results for those search parameters."
|
empty: "There are no results for those search parameters."
|
||||||
caption: Post list
|
caption: Post list
|
||||||
|
filters:
|
||||||
|
title: Filters
|
||||||
|
submit: Submit
|
||||||
attribute_ro:
|
attribute_ro:
|
||||||
file:
|
file:
|
||||||
download: Download file
|
download: Download file
|
||||||
|
@ -746,6 +745,12 @@ en:
|
||||||
image:
|
image:
|
||||||
label: Image
|
label: Image
|
||||||
destroy: Remove image
|
destroy: Remove image
|
||||||
|
audio:
|
||||||
|
label: Audio file
|
||||||
|
logo:
|
||||||
|
label: Logo
|
||||||
|
download:
|
||||||
|
label: Archivo
|
||||||
belongs_to:
|
belongs_to:
|
||||||
empty: "(Empty)"
|
empty: "(Empty)"
|
||||||
predefined_value:
|
predefined_value:
|
||||||
|
@ -766,7 +771,7 @@ en:
|
||||||
date: 'date'
|
date: 'date'
|
||||||
order: 'Order'
|
order: 'Order'
|
||||||
content: 'Text'
|
content: 'Text'
|
||||||
new: 'Post types'
|
new: 'Add content'
|
||||||
remove_filter_help: 'Remove the filter: %{filter}'
|
remove_filter_help: 'Remove the filter: %{filter}'
|
||||||
categories: 'Everything'
|
categories: 'Everything'
|
||||||
index:
|
index:
|
||||||
|
@ -923,14 +928,14 @@ en:
|
||||||
queries:
|
queries:
|
||||||
show:
|
show:
|
||||||
empty: '(empty)'
|
empty: '(empty)'
|
||||||
|
build_stats:
|
||||||
|
index:
|
||||||
|
title: "Publications"
|
||||||
schemas:
|
schemas:
|
||||||
add:
|
add:
|
||||||
add: 'Add'
|
add: 'Add'
|
||||||
filter:
|
filter:
|
||||||
filter: 'Filter'
|
filter: 'Filter'
|
||||||
remove: 'Back'
|
remove: 'Back'
|
||||||
build_stats:
|
|
||||||
index:
|
|
||||||
title: "Publications"
|
|
||||||
indexed_posts:
|
indexed_posts:
|
||||||
deleted: "Deleted indexed post %{path} from %{site} (records: %{records})"
|
deleted: "Deleted indexed post %{path} from %{site} (records: %{records})"
|
||||||
|
|
|
@ -317,10 +317,6 @@ es:
|
||||||
title: Fediverso
|
title: Fediverso
|
||||||
success: ¡Éxito!
|
success: ¡Éxito!
|
||||||
error: Hubo un error
|
error: Hubo un error
|
||||||
deploy_reindex:
|
|
||||||
title: Reindexación
|
|
||||||
success: ¡Éxito!
|
|
||||||
error: Hubo un error
|
|
||||||
deploy_localized_domain:
|
deploy_localized_domain:
|
||||||
title: Dominio según idioma
|
title: Dominio según idioma
|
||||||
success: ¡Éxito!
|
success: ¡Éxito!
|
||||||
|
@ -329,12 +325,12 @@ es:
|
||||||
title: Sincronizar al servidor alternativo
|
title: Sincronizar al servidor alternativo
|
||||||
success: ¡Éxito!
|
success: ¡Éxito!
|
||||||
error: Hubo un error
|
error: Hubo un error
|
||||||
deploy_full_rsync:
|
deploy_reindex:
|
||||||
title: Sincronizar a otro nodo de Sutty
|
title: Reindexación
|
||||||
success: ¡Éxito!
|
success: ¡Éxito!
|
||||||
error: Hubo un error
|
error: Hubo un error
|
||||||
deploy_distributed_press:
|
deploy_full_rsync:
|
||||||
title: Web distribuida
|
title: Sincronizar a otro nodo de Sutty
|
||||||
success: ¡Éxito!
|
success: ¡Éxito!
|
||||||
error: Hubo un error
|
error: Hubo un error
|
||||||
help: Por cualquier duda, responde este correo para contactarte con nosotres.
|
help: Por cualquier duda, responde este correo para contactarte con nosotres.
|
||||||
|
@ -637,7 +633,7 @@ es:
|
||||||
help:
|
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.'
|
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.'
|
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.'
|
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
|
licencia: 'Todo lo que publicamos posee automáticamente derechos
|
||||||
de autore. Esto significa que nadie puede hacer uso de nuestras
|
de autore. Esto significa que nadie puede hacer uso de nuestras
|
||||||
|
@ -717,6 +713,9 @@ es:
|
||||||
next: Página siguiente
|
next: Página siguiente
|
||||||
empty: No hay artículos con estos parámetros de búsqueda.
|
empty: No hay artículos con estos parámetros de búsqueda.
|
||||||
caption: Lista de artículos
|
caption: Lista de artículos
|
||||||
|
filters:
|
||||||
|
title: Filtros
|
||||||
|
submit: Aplicar
|
||||||
attribute_ro:
|
attribute_ro:
|
||||||
file:
|
file:
|
||||||
download: Descargar archivo
|
download: Descargar archivo
|
||||||
|
@ -754,6 +753,12 @@ es:
|
||||||
image:
|
image:
|
||||||
label: Imagen
|
label: Imagen
|
||||||
destroy: 'Eliminar imagen'
|
destroy: 'Eliminar imagen'
|
||||||
|
logo:
|
||||||
|
label: Logo
|
||||||
|
audio:
|
||||||
|
label: Audio
|
||||||
|
download:
|
||||||
|
label: Archivo
|
||||||
belongs_to:
|
belongs_to:
|
||||||
empty: "(Vacío)"
|
empty: "(Vacío)"
|
||||||
predefined_value:
|
predefined_value:
|
||||||
|
@ -775,7 +780,7 @@ es:
|
||||||
order: 'Posición'
|
order: 'Posición'
|
||||||
content: 'Cuerpo del artículo'
|
content: 'Cuerpo del artículo'
|
||||||
categories: 'Todos'
|
categories: 'Todos'
|
||||||
new: 'Tipos de artículos'
|
new: 'Agregar contenido'
|
||||||
remove_filter_help: 'Quitar este filtro: %{filter}'
|
remove_filter_help: 'Quitar este filtro: %{filter}'
|
||||||
index:
|
index:
|
||||||
search: 'Buscar'
|
search: 'Buscar'
|
||||||
|
@ -931,14 +936,14 @@ es:
|
||||||
queries:
|
queries:
|
||||||
show:
|
show:
|
||||||
empty: '(vacío)'
|
empty: '(vacío)'
|
||||||
|
build_stats:
|
||||||
|
index:
|
||||||
|
title: "Publicaciones"
|
||||||
schemas:
|
schemas:
|
||||||
add:
|
add:
|
||||||
add: 'Agregar'
|
add: 'Agregar'
|
||||||
filter:
|
filter:
|
||||||
filter: 'Filtrar'
|
filter: 'Filtrar'
|
||||||
remove: 'Volver'
|
remove: 'Volver'
|
||||||
build_stats:
|
|
||||||
index:
|
|
||||||
title: "Publicaciones"
|
|
||||||
indexed_posts:
|
indexed_posts:
|
||||||
deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})"
|
deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})"
|
||||||
|
|
|
@ -27,7 +27,7 @@ class CreateIndexedPosts < ActiveRecord::Migration[6.1]
|
||||||
# Queremos mostrar el título por separado
|
# Queremos mostrar el título por separado
|
||||||
t.string :title, default: ''
|
t.string :title, default: ''
|
||||||
# También vamos a mostrar las categorías
|
# También vamos a mostrar las categorías
|
||||||
t.jsonb :front_matter, default: '{}'
|
t.jsonb :front_matter, default: {}
|
||||||
t.string :content, default: ''
|
t.string :content, default: ''
|
||||||
t.tsvector :indexed_content
|
t.tsvector :indexed_content
|
||||||
|
|
||||||
|
|
10
db/migrate/20231026211607_deprecate_deploy_reindex.rb
Normal file
10
db/migrate/20231026211607_deprecate_deploy_reindex.rb
Normal file
|
@ -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
|
|
@ -9,6 +9,27 @@ SET xmloption = content;
|
||||||
SET client_min_messages = warning;
|
SET client_min_messages = warning;
|
||||||
SET row_security = off;
|
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: -
|
-- 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);
|
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
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
|
@ -15,7 +15,7 @@ check program fediblocks
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
||||||
check program access_logs
|
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 * * *"
|
every "0 0 * * *"
|
||||||
if status != 0 then alert
|
if status != 0 then alert
|
||||||
|
|
||||||
|
|
BIN
public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json
(Stored with Git LFS)
Normal file
BIN
public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
BIN
public/assets/.sprockets-manifest-c6294bb290dcb7473076f4de99ce9c00.json
(Stored with Git LFS)
Binary file not shown.
BIN
public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-20ccc989c289957de41530f1a6c63720c6980e71b85b2b73698ee2ad85e39788.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-4036902ca82a9c42d78f1ddc26a430aad6ff784e65914ef1219a2a1fc3150690.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-5c98ae5b7e5b4444349f4c0175aa88fb76292284e1c68bfc4724151ceaf5113a.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-66970ebaf46dd4256bcad9e73d5873abc104a84e04250f65e97902e27f4384ff.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css
(Stored with Git LFS)
Normal file
BIN
public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css.gz
(Stored with Git LFS)
Normal file
BIN
public/assets/application-c675edc8163d1c94a6045443ef27d9982be94608a1c154d5d6ae413ce9c5c60a.css.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue