mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-16 05:31:42 +00:00
Merge branch 'rails' into void/editor
This commit is contained in:
commit
0b86702bdb
38 changed files with 378 additions and 357 deletions
6
Gemfile
6
Gemfile
|
@ -5,8 +5,11 @@
|
||||||
# tiempo buscando soporte para musl
|
# tiempo buscando soporte para musl
|
||||||
if ENV['RAILS_ENV'] == 'production'
|
if ENV['RAILS_ENV'] == 'production'
|
||||||
source 'https://gems.sutty.nl'
|
source 'https://gems.sutty.nl'
|
||||||
|
ruby '2.7.2'
|
||||||
else
|
else
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
# Cambiar en Dockerfile también
|
||||||
|
ruby '2.7.1'
|
||||||
end
|
end
|
||||||
|
|
||||||
git_source(:github) do |repo_name|
|
git_source(:github) do |repo_name|
|
||||||
|
@ -14,8 +17,6 @@ git_source(:github) do |repo_name|
|
||||||
"https://github.com/#{repo_name}.git"
|
"https://github.com/#{repo_name}.git"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Cambiar en Dockerfile también
|
|
||||||
ruby '2.7.2'
|
|
||||||
|
|
||||||
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
gem 'dotenv-rails', require: 'dotenv/rails-now'
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@ gem 'redis', require: %w[redis redis/connection/hiredis]
|
||||||
gem 'redis-rails'
|
gem 'redis-rails'
|
||||||
gem 'rubyzip'
|
gem 'rubyzip'
|
||||||
gem 'rugged'
|
gem 'rugged'
|
||||||
|
gem 'concurrent-ruby-ext'
|
||||||
gem 'sucker_punch'
|
gem 'sucker_punch'
|
||||||
gem 'symbol-fstring', require: 'fstring/all'
|
gem 'symbol-fstring', require: 'fstring/all'
|
||||||
gem 'terminal-table'
|
gem 'terminal-table'
|
||||||
|
|
132
Gemfile.lock
132
Gemfile.lock
|
@ -10,60 +10,60 @@ GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
remote: https://gems.sutty.nl/
|
remote: https://gems.sutty.nl/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.1)
|
actioncable (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.1)
|
actionmailbox (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
activejob (= 6.1.1)
|
activejob (= 6.1.2.1)
|
||||||
activerecord (= 6.1.1)
|
activerecord (= 6.1.2.1)
|
||||||
activestorage (= 6.1.1)
|
activestorage (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.1)
|
actionmailer (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
actionview (= 6.1.1)
|
actionview (= 6.1.2.1)
|
||||||
activejob (= 6.1.1)
|
activejob (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.1)
|
actionpack (6.1.2.1)
|
||||||
actionview (= 6.1.1)
|
actionview (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.1)
|
actiontext (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
activerecord (= 6.1.1)
|
activerecord (= 6.1.2.1)
|
||||||
activestorage (= 6.1.1)
|
activestorage (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.1)
|
actionview (6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activejob (6.1.1)
|
activejob (6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.1)
|
activemodel (6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
activerecord (6.1.1)
|
activerecord (6.1.2.1)
|
||||||
activemodel (= 6.1.1)
|
activemodel (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
activestorage (6.1.1)
|
activestorage (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
activejob (= 6.1.1)
|
activejob (= 6.1.2.1)
|
||||||
activerecord (= 6.1.1)
|
activerecord (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
marcel (~> 0.3.1)
|
marcel (~> 0.3.1)
|
||||||
mimemagic (~> 0.3.2)
|
mimemagic (~> 0.3.2)
|
||||||
activesupport (6.1.1)
|
activesupport (6.1.2.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -86,7 +86,7 @@ GEM
|
||||||
bcrypt (3.1.16)
|
bcrypt (3.1.16)
|
||||||
benchmark-ips (2.8.4)
|
benchmark-ips (2.8.4)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
blazer (2.4.1)
|
blazer (2.4.2)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
chartkick (>= 3.2)
|
chartkick (>= 3.2)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
|
@ -108,18 +108,20 @@ GEM
|
||||||
childprocess (3.0.0)
|
childprocess (3.0.0)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
commonmarker (0.21.1)
|
commonmarker (0.21.2)
|
||||||
ruby-enum (~> 0.5)
|
ruby-enum (~> 0.5)
|
||||||
concurrent-ruby (1.1.8)
|
concurrent-ruby (1.1.8)
|
||||||
|
concurrent-ruby-ext (1.1.8)
|
||||||
|
concurrent-ruby (= 1.1.8)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
database_cleaner (2.0.0)
|
database_cleaner (2.0.1)
|
||||||
database_cleaner-active_record (~> 2.0.0)
|
database_cleaner-active_record (~> 2.0.0)
|
||||||
database_cleaner-active_record (2.0.0)
|
database_cleaner-active_record (2.0.0)
|
||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.0)
|
database_cleaner-core (2.0.1)
|
||||||
dead_end (1.1.4)
|
dead_end (1.1.4)
|
||||||
derailed_benchmarks (2.0.0)
|
derailed_benchmarks (2.0.1)
|
||||||
benchmark-ips (~> 2)
|
benchmark-ips (~> 2)
|
||||||
dead_end
|
dead_end
|
||||||
get_process_mem (~> 0)
|
get_process_mem (~> 0)
|
||||||
|
@ -127,6 +129,7 @@ GEM
|
||||||
memory_profiler (>= 0, < 2)
|
memory_profiler (>= 0, < 2)
|
||||||
mini_histogram (>= 0.3.0)
|
mini_histogram (>= 0.3.0)
|
||||||
rack (>= 1)
|
rack (>= 1)
|
||||||
|
rack-test
|
||||||
rake (> 10, < 14)
|
rake (> 10, < 14)
|
||||||
ruby-statistics (>= 2.1)
|
ruby-statistics (>= 2.1)
|
||||||
thor (>= 0.19, < 2)
|
thor (>= 0.19, < 2)
|
||||||
|
@ -287,7 +290,7 @@ GEM
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
ruby_dep (~> 1.2)
|
ruby_dep (~> 1.2)
|
||||||
lockbox (0.6.1)
|
lockbox (0.6.2)
|
||||||
lograge (0.11.2)
|
lograge (0.11.2)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
|
@ -313,7 +316,7 @@ GEM
|
||||||
jekyll-feed (~> 0.9)
|
jekyll-feed (~> 0.9)
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.14.3)
|
minitest (5.14.3)
|
||||||
mobility (1.0.5)
|
mobility (1.1.0)
|
||||||
i18n (>= 0.6.10, < 2)
|
i18n (>= 0.6.10, < 2)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
netaddr (2.0.4)
|
netaddr (2.0.4)
|
||||||
|
@ -331,7 +334,7 @@ GEM
|
||||||
popper_js (1.16.0)
|
popper_js (1.16.0)
|
||||||
prometheus_exporter (0.7.0)
|
prometheus_exporter (0.7.0)
|
||||||
webrick
|
webrick
|
||||||
pry (0.13.1)
|
pry (0.14.0)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
|
@ -360,20 +363,20 @@ GEM
|
||||||
jekyll-relative-urls (~> 0.0)
|
jekyll-relative-urls (~> 0.0)
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
jekyll-turbolinks (~> 0)
|
jekyll-turbolinks (~> 0)
|
||||||
rails (6.1.1)
|
rails (6.1.2.1)
|
||||||
actioncable (= 6.1.1)
|
actioncable (= 6.1.2.1)
|
||||||
actionmailbox (= 6.1.1)
|
actionmailbox (= 6.1.2.1)
|
||||||
actionmailer (= 6.1.1)
|
actionmailer (= 6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
actiontext (= 6.1.1)
|
actiontext (= 6.1.2.1)
|
||||||
actionview (= 6.1.1)
|
actionview (= 6.1.2.1)
|
||||||
activejob (= 6.1.1)
|
activejob (= 6.1.2.1)
|
||||||
activemodel (= 6.1.1)
|
activemodel (= 6.1.2.1)
|
||||||
activerecord (= 6.1.1)
|
activerecord (= 6.1.2.1)
|
||||||
activestorage (= 6.1.1)
|
activestorage (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.1)
|
railties (= 6.1.2.1)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
|
@ -385,9 +388,9 @@ GEM
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 7)
|
||||||
rails_warden (0.6.0)
|
rails_warden (0.6.0)
|
||||||
warden (>= 1.2.0)
|
warden (>= 1.2.0)
|
||||||
railties (6.1.1)
|
railties (6.1.2.1)
|
||||||
actionpack (= 6.1.1)
|
actionpack (= 6.1.2.1)
|
||||||
activesupport (= 6.1.1)
|
activesupport (= 6.1.2.1)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
@ -457,7 +460,7 @@ GEM
|
||||||
i18n
|
i18n
|
||||||
ruby-filemagic (0.7.2)
|
ruby-filemagic (0.7.2)
|
||||||
ruby-progressbar (1.11.0)
|
ruby-progressbar (1.11.0)
|
||||||
ruby-statistics (2.1.2)
|
ruby-statistics (2.1.3)
|
||||||
ruby-vips (2.0.17)
|
ruby-vips (2.0.17)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
ruby_dep (1.5.0)
|
ruby_dep (1.5.0)
|
||||||
|
@ -537,7 +540,7 @@ GEM
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thor (1.1.0)
|
thor (1.1.0)
|
||||||
tilt (2.0.10)
|
tilt (2.0.10)
|
||||||
timecop (0.9.2)
|
timecop (0.9.4)
|
||||||
turbolinks (5.2.1)
|
turbolinks (5.2.1)
|
||||||
turbolinks-source (~> 5.2)
|
turbolinks-source (~> 5.2)
|
||||||
turbolinks-source (5.2.0)
|
turbolinks-source (5.2.0)
|
||||||
|
@ -583,6 +586,7 @@ DEPENDENCIES
|
||||||
brakeman
|
brakeman
|
||||||
capybara (~> 2.13)
|
capybara (~> 2.13)
|
||||||
commonmarker
|
commonmarker
|
||||||
|
concurrent-ruby-ext
|
||||||
database_cleaner
|
database_cleaner
|
||||||
derailed_benchmarks
|
derailed_benchmarks
|
||||||
devise
|
devise
|
||||||
|
@ -660,7 +664,7 @@ DEPENDENCIES
|
||||||
yaml_db!
|
yaml_db!
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.7.2p137
|
ruby 2.7.1p83
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.1.4
|
2.1.4
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -9,10 +9,10 @@ assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type
|
||||||
|
|
||||||
alpine_version := 3.12
|
alpine_version := 3.12
|
||||||
|
|
||||||
public/packs/manifest.json: $(assets)
|
public/packs/manifest.json.br: $(assets)
|
||||||
PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean
|
PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean
|
||||||
|
|
||||||
assets: public/packs/manifest.json
|
assets: public/packs/manifest.json.br
|
||||||
|
|
||||||
serve: /etc/hosts
|
serve: /etc/hosts
|
||||||
bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"
|
bundle exec rails s -b "ssl://0.0.0.0:3000?key=../sutty.local/domain/$(SUTTY).key&cert=../sutty.local/domain/$(SUTTY).crt"
|
||||||
|
@ -82,7 +82,7 @@ $(dirs):
|
||||||
/etc/hosts: always
|
/etc/hosts: always
|
||||||
@echo "Chequeando si es necesario agregar el dominio local $(SUTTY)"
|
@echo "Chequeando si es necesario agregar el dominio local $(SUTTY)"
|
||||||
@grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
|
@grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
|
||||||
@grep -q " api.$(SUTTY)$$" $@ || echo -e "127.0.0.1 api.$(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
|
@grep -q " api.$(SUTTY)$$" $@ || echo -e "127.0.0.1 api.$(SUTTY)\n::1 api.$(SUTTY)" | sudo tee -a $@
|
||||||
@grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
|
@grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@
|
||||||
|
|
||||||
.PHONY: always
|
.PHONY: always
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
include ExceptionHandler
|
include ExceptionHandler
|
||||||
|
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception, prepend: true
|
||||||
|
|
||||||
before_action :prepare_exception_notifier
|
before_action :prepare_exception_notifier
|
||||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||||
|
|
|
@ -73,7 +73,7 @@ module ApplicationHelper
|
||||||
|
|
||||||
# Opciones por defecto para el campo de un formulario
|
# Opciones por defecto para el campo de un formulario
|
||||||
def field_options(attribute, metadata, **extra)
|
def field_options(attribute, metadata, **extra)
|
||||||
required = metadata.required || extra[:required]
|
required = extra.key?(:required) ? extra[:required] : metadata.required
|
||||||
|
|
||||||
{
|
{
|
||||||
class: "form-control #{invalid(metadata.post, attribute)} #{extra[:class]}",
|
class: "form-control #{invalid(metadata.post, attribute)} #{extra[:class]}",
|
||||||
|
|
|
@ -107,4 +107,13 @@ document.addEventListener('turbolinks:load', () => {
|
||||||
// Ocultar el area
|
// Ocultar el area
|
||||||
textArea.style.display = 'none'
|
textArea.style.display = 'none'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Validar fechas en navegadores que no soportan date
|
||||||
|
document.querySelectorAll('input[type="date"]').forEach(date => {
|
||||||
|
if (date.type === 'date') return
|
||||||
|
|
||||||
|
date.addEventListener('change', event => {
|
||||||
|
date.setCustomValidity(date.validity.patternMismatch ? date.dataset.patternMismatch : '')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,15 +22,13 @@ class MaintenanceJob < ApplicationJob
|
||||||
|
|
||||||
# XXX: Parece que [0] es más rápido que []#first
|
# XXX: Parece que [0] es más rápido que []#first
|
||||||
Usuarie.all.pluck(:email, :lang).each do |u|
|
Usuarie.all.pluck(:email, :lang).each do |u|
|
||||||
begin
|
|
||||||
MaintenanceMailer.with(maintenance: maintenance,
|
MaintenanceMailer.with(maintenance: maintenance,
|
||||||
email: u[0],
|
email: u[0],
|
||||||
lang: u[1]).public_send(mailer).deliver_now
|
lang: u[1]).public_send(mailer).deliver_now
|
||||||
rescue Net::SMTPServerBusy => e
|
rescue Net::SMTPServerBusy => e
|
||||||
# Algunas direcciones no son válidas, no queremos detener el
|
# Algunas direcciones no son válidas, no queremos detener el
|
||||||
# envío pero sí enterarnos cuáles son
|
# envío pero sí enterarnos cuáles son
|
||||||
ExceptionNotifier.notify_exception e
|
ExceptionNotifier.notify_exception(e, data: { maintenance_id: maintenance_id, email: u[0] })
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,10 @@ Layout = Struct.new(:site, :name, :meta, :metadata, keyword_init: true) do
|
||||||
name.to_s
|
name.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
@attributes ||= metadata.keys.map(&:to_sym)
|
||||||
|
end
|
||||||
|
|
||||||
# Busca la traducción del Layout en el sitio o intenta humanizarlo
|
# Busca la traducción del Layout en el sitio o intenta humanizarlo
|
||||||
# según Rails.
|
# según Rails.
|
||||||
#
|
#
|
||||||
|
|
|
@ -7,15 +7,22 @@ class MetadataArray < MetadataTemplate
|
||||||
super || []
|
super || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Los Arrays no se pueden cifrar todavía
|
||||||
|
# TODO: Cifrar y decifrar arrays
|
||||||
|
def private?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# TODO: Sanitizar otros valores
|
||||||
|
# XXX: Por qué eliminamos el punto del final?
|
||||||
def sanitize(values)
|
def sanitize(values)
|
||||||
values.map do |v|
|
values.map do |v|
|
||||||
if v.is_a? String
|
case v
|
||||||
super(v).sub(/\.\z/, '')
|
when String then super(v).sub(/\.\z/, '')
|
||||||
else
|
else v
|
||||||
v
|
end
|
||||||
end
|
end.select(&:present?)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
# 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
|
||||||
|
def value_was=(new_value)
|
||||||
|
@belongs_to = nil
|
||||||
|
@belonged_to = nil
|
||||||
|
|
||||||
|
super(new_value)
|
||||||
|
end
|
||||||
|
|
||||||
# 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
|
||||||
#
|
#
|
||||||
|
@ -23,36 +30,36 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
|
|
||||||
# Guardar y guardar la relación inversa también, eliminando la
|
# Guardar y guardar la relación inversa también, eliminando la
|
||||||
# relación anterior si existía.
|
# relación anterior si existía.
|
||||||
#
|
|
||||||
# XXX: Esto es un poco enclenque, porque habría que guardar tres
|
|
||||||
# archivos en lugar de uno solo e indicarle al artículo que tiene uno
|
|
||||||
# o muchos que busque los datos actualizados filtrando. Pero también
|
|
||||||
# nos ahorra recursos en la búsqueda al cachear la información. En
|
|
||||||
# una relación HABTM también vamos a hacer lo mismo.
|
|
||||||
def save
|
def save
|
||||||
return super unless inverse? && !included?
|
super
|
||||||
|
|
||||||
# Evitar que se cambie el orden de la relación
|
# Si no hay relación inversa, no hacer nada más
|
||||||
belonged_to&.dig(inverse)&.value&.delete post.uuid.value if belonged_to != belongs_to
|
return true unless changed?
|
||||||
|
return true unless inverse?
|
||||||
|
|
||||||
belongs_to[inverse].value << post.uuid.value unless belongs_to[inverse].value.include? post.uuid.value
|
# Si estamos cambiando la relación, tenemos que eliminar la relación
|
||||||
|
# anterior
|
||||||
|
belonged_to[inverse].value.delete post.uuid.value if changed? && belonged_to.present?
|
||||||
|
|
||||||
|
# No duplicar las relaciones
|
||||||
|
belongs_to[inverse].value << post.uuid.value unless belongs_to.blank? || included?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# El Post actual está incluido en la relación inversa?
|
# El Post actual está incluido en la relación inversa?
|
||||||
def included?
|
def included?
|
||||||
belongs_to[inverse].value.include? post.uuid.value
|
belongs_to[inverse].value.include?(post.uuid.value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Hay una relación inversa y el artículo existe?
|
# Hay una relación inversa y el artículo existe?
|
||||||
def inverse?
|
def inverse?
|
||||||
inverse.present? && belongs_to.present?
|
inverse.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
# El campo que es la relación inversa de este
|
# El campo que es la relación inversa de este
|
||||||
def inverse
|
def inverse
|
||||||
layout.metadata.dig name, 'inverse'
|
@inverse ||= layout.metadata.dig(name, 'inverse')&.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
# El Post relacionado con este artículo
|
# El Post relacionado con este artículo
|
||||||
|
@ -62,15 +69,14 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
def belongs_to
|
def belongs_to
|
||||||
return if value.blank?
|
return if value.blank?
|
||||||
|
|
||||||
@belongs_to ||= {}
|
@belongs_to ||= posts.find(value, uuid: true)
|
||||||
@belongs_to[value] ||= posts.find(value, uuid: true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# El anterior artículo relacionado
|
# El artículo relacionado anterior
|
||||||
def belonged_to
|
def belonged_to
|
||||||
return if document.data[name.to_s].blank?
|
return if value_was.blank?
|
||||||
|
|
||||||
@belonged_to ||= posts.find(document.data[name.to_s], uuid: true)
|
@belonged_to ||= posts.find(value_was, uuid: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def related_posts?
|
def related_posts?
|
||||||
|
@ -83,11 +89,13 @@ class MetadataBelongsTo < MetadataRelatedPosts
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def sanitize(uuid)
|
def post_exists?
|
||||||
uuid.gsub(/[^a-f0-9\-]/, '')
|
return true if sanitize(value).blank?
|
||||||
|
|
||||||
|
sanitize(value).present? && belongs_to.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_exists?
|
def sanitize(uuid)
|
||||||
!value.blank? && posts.find(sanitize(value), uuid: true)
|
uuid.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,10 +7,14 @@ class MetadataDocumentDate < MetadataTemplate
|
||||||
Date.today.to_time
|
Date.today.to_time
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def value_from_document
|
||||||
|
document.date
|
||||||
|
end
|
||||||
|
|
||||||
# El valor puede ser un Date, Time o una String en el formato
|
# El valor puede ser un Date, Time o una String en el formato
|
||||||
# "yyyy-mm-dd"
|
# "yyyy-mm-dd"
|
||||||
def value
|
def value
|
||||||
return (self[:value] = document.date || default_value) if self[:value].nil?
|
return (self[:value] = value_from_document || default_value) if self[:value].nil?
|
||||||
|
|
||||||
self[:value] = Date.iso8601(self[:value]).to_time if self[:value].is_a? String
|
self[:value] = Date.iso8601(self[:value]).to_time if self[:value].is_a? String
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,14 @@
|
||||||
|
|
||||||
# Devuelve metadatos de cierto tipo
|
# Devuelve metadatos de cierto tipo
|
||||||
class MetadataFactory
|
class MetadataFactory
|
||||||
def self.build(**args)
|
class << self
|
||||||
@@factory_cache ||= {}
|
def build(**args)
|
||||||
@@factory_cache[args[:type]] ||= ('Metadata' + args[:type].to_s.camelcase).constantize
|
classify(args[:type]).new(**args)
|
||||||
@@factory_cache[args[:type]].new(args)
|
end
|
||||||
|
|
||||||
|
def classify(type)
|
||||||
|
@factory_cache ||= {}
|
||||||
|
@factory_cache[type] ||= ('Metadata' + type.to_s.camelcase).constantize
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,10 +73,14 @@ class MetadataFile < MetadataTemplate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def key_from_path
|
||||||
|
path.dirname.basename.to_s
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def path?
|
def path?
|
||||||
!value['path'].blank?
|
value['path'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def filemagic
|
def filemagic
|
||||||
|
@ -100,10 +104,6 @@ class MetadataFile < MetadataTemplate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def key_from_path
|
|
||||||
path.dirname.basename.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
||||||
# y no duplicar el espacio que ocupan. Esto requiere que ambos
|
# y no duplicar el espacio que ocupan. Esto requiere que ambos
|
||||||
# directorios estén dentro del mismo punto de montaje.
|
# directorios estén dentro del mismo punto de montaje.
|
||||||
|
|
|
@ -10,40 +10,7 @@
|
||||||
# el libro actual. La relación belongs_to tiene que traer todes les
|
# 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
|
# autores que tienen este libro. La relación es bidireccional, no hay
|
||||||
# diferencia entre has_many y belongs_to.
|
# diferencia entre has_many y belongs_to.
|
||||||
class MetadataHasAndBelongsToMany < MetadataBelongsTo
|
class MetadataHasAndBelongsToMany < MetadataHasMany
|
||||||
def default_value
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Posts a los que pertenece. Memoizamos por value para obtener
|
|
||||||
# siempre la última relación.
|
|
||||||
#
|
|
||||||
# Buscamos todos los Post contenidos en el valor actual. No
|
|
||||||
# garantizamos el orden.
|
|
||||||
#
|
|
||||||
# @return [PostRelation] Posts
|
|
||||||
def belongs_to
|
|
||||||
@belongs_to ||= {}
|
|
||||||
@belongs_to[value.hash.to_s] ||= posts.where(uuid: value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Devuelve la lista de Posts relacionados con este buscándolos en la
|
|
||||||
# relación inversa. #save debería mantenerlos sincronizados.
|
|
||||||
#
|
|
||||||
# @return [PostRelation]
|
|
||||||
def has_many
|
|
||||||
@has_many ||= {}
|
|
||||||
@has_many[value.hash.to_s] ||= posts.where(inverse.to_sym => post.uuid.value)
|
|
||||||
end
|
|
||||||
alias had_many has_many
|
|
||||||
|
|
||||||
# Posts a los que pertenecía
|
|
||||||
#
|
|
||||||
# @return [PostRelation] Posts
|
|
||||||
def belonged_to
|
|
||||||
@belonged_to ||= posts.where(uuid: document.data.fetch(name.to_s, []))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Mantiene la relación inversa si existe.
|
# Mantiene la relación inversa si existe.
|
||||||
#
|
#
|
||||||
# La relación belongs_to se mantiene actualizada en la modificación
|
# La relación belongs_to se mantiene actualizada en la modificación
|
||||||
|
@ -52,39 +19,33 @@ class MetadataHasAndBelongsToMany < MetadataBelongsTo
|
||||||
# Buscamos en belongs_to la relación local, si se eliminó hay que
|
# Buscamos en belongs_to la relación local, si se eliminó hay que
|
||||||
# quitarla de la relación remota, sino hay que agregarla.
|
# quitarla de la relación remota, sino hay que agregarla.
|
||||||
def save
|
def save
|
||||||
return true unless changed?
|
# XXX: No usamos super
|
||||||
|
|
||||||
self[:value] = sanitize value
|
self[:value] = sanitize value
|
||||||
|
|
||||||
return true unless inverse? && !included?
|
return true unless changed?
|
||||||
|
return true unless inverse?
|
||||||
|
|
||||||
(belonged_to - belongs_to).each do |p|
|
(had_many - has_many).each do |remove|
|
||||||
p[inverse].value.delete post.uuid.value
|
remove[inverse]&.value&.delete post.uuid.value
|
||||||
end
|
end
|
||||||
|
|
||||||
(belongs_to - belonged_to).each do |p|
|
(has_many - had_many).each do |add|
|
||||||
p[inverse].value << post.uuid.value
|
next unless add[inverse]
|
||||||
|
next if add[inverse].value.include? post.uuid.value
|
||||||
|
|
||||||
|
add[inverse].value << post.uuid.value
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize(sanitizable)
|
private
|
||||||
sanitizable.map do |v|
|
|
||||||
super v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def post_exists?
|
# Igual que en MetadataRelatedPosts
|
||||||
return true if empty? && can_be_empty?
|
# TODO: Mover a un módulo
|
||||||
|
def sanitize(uuid)
|
||||||
!belongs_to.empty?
|
super(uuid.map do |u|
|
||||||
end
|
u.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||||
|
end)
|
||||||
# Todos los artículos relacionados incluyen a este?
|
|
||||||
def included?
|
|
||||||
belongs_to.map do |p|
|
|
||||||
p[inverse].value.include? post.uuid.value
|
|
||||||
end.all?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,22 +6,32 @@
|
||||||
# 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 según la relación remota
|
# Invalidar la relación anterior
|
||||||
def has_many_remote
|
def value_was=(new_value)
|
||||||
@has_many_remote ||= posts.where(inverse => post.uuid.value)
|
@had_many = nil
|
||||||
|
@has_many = nil
|
||||||
|
|
||||||
|
super(new_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate
|
||||||
|
super
|
||||||
|
|
||||||
|
errors << I18n.t('metadata.has_many.missing_posts') unless posts_exist?
|
||||||
|
|
||||||
|
errors.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Todos los Post relacionados
|
# Todos los Post relacionados
|
||||||
def has_many
|
def has_many
|
||||||
@has_many ||= {}
|
@has_many ||= posts.where(uuid: value)
|
||||||
@has_many[value.hash.to_s] ||= posts.where(uuid: value)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# La relación anterior
|
# La relación anterior
|
||||||
def had_many
|
def had_many
|
||||||
return [] if document.data[name.to_s].blank?
|
return [] if value_was.blank?
|
||||||
|
|
||||||
@had_many ||= posts.where(uuid: document.data[name.to_s])
|
@had_many ||= posts.where(uuid: value_was)
|
||||||
end
|
end
|
||||||
|
|
||||||
def inverse?
|
def inverse?
|
||||||
|
@ -38,18 +48,17 @@ class MetadataHasMany < MetadataRelatedPosts
|
||||||
# Actualizar las relaciones inversas. Hay que buscar la diferencia
|
# Actualizar las relaciones inversas. Hay que buscar la diferencia
|
||||||
# entre had y has_many.
|
# entre had y has_many.
|
||||||
def save
|
def save
|
||||||
|
super
|
||||||
|
|
||||||
return true unless changed?
|
return true unless changed?
|
||||||
|
|
||||||
self[:value] = sanitize value
|
|
||||||
|
|
||||||
return true unless inverse?
|
return true unless inverse?
|
||||||
|
|
||||||
(had_many - has_many).each do |remove|
|
(had_many - has_many).each do |remove|
|
||||||
remove[inverse].value = remove[inverse].default_value
|
remove[inverse]&.value = remove[inverse].default_value
|
||||||
end
|
end
|
||||||
|
|
||||||
(has_many - had_many).each do |add|
|
(has_many - had_many).each do |add|
|
||||||
add[inverse].value = post.uuid.value
|
add[inverse]&.value = post.uuid.value
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -63,11 +72,7 @@ class MetadataHasMany < MetadataRelatedPosts
|
||||||
@related_methods ||= %i[has_many had_many].freeze
|
@related_methods ||= %i[has_many had_many].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def posts_exist?
|
||||||
|
has_many.size == sanitize(value).size
|
||||||
def sanitize(sanitizable)
|
|
||||||
sanitizable.map do |uuid|
|
|
||||||
uuid.gsub(/[^a-f0-9\-]/, '')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,12 @@ class MetadataLang < MetadataTemplate
|
||||||
super || I18n.locale
|
super || I18n.locale
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def value_from_document
|
||||||
|
document.collection.label
|
||||||
|
end
|
||||||
|
|
||||||
def value
|
def value
|
||||||
self[:value] ||= document.collection.label || default_value
|
self[:value] ||= value_from_document || default_value
|
||||||
end
|
end
|
||||||
|
|
||||||
def values
|
def values
|
||||||
|
|
|
@ -7,8 +7,12 @@ class MetadataPath < MetadataTemplate
|
||||||
File.join(site.path, "_#{lang}", "#{date}-#{slug}#{ext}")
|
File.join(site.path, "_#{lang}", "#{date}-#{slug}#{ext}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# El valor no vuelve desde el documento
|
||||||
|
def value_from_document
|
||||||
|
document.path
|
||||||
|
end
|
||||||
|
|
||||||
def value
|
def value
|
||||||
@value_was ||= default_value
|
|
||||||
self[:value] = default_value
|
self[:value] = default_value
|
||||||
end
|
end
|
||||||
alias absolute value
|
alias absolute value
|
||||||
|
|
|
@ -31,4 +31,10 @@ class MetadataRelatedPosts < MetadataArray
|
||||||
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)
|
||||||
|
super(uuid.map do |u|
|
||||||
|
u.to_s.gsub(/[^a-f0-9\-]/i, '')
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,11 +25,7 @@ require 'jekyll/utils'
|
||||||
class MetadataSlug < MetadataTemplate
|
class MetadataSlug < MetadataTemplate
|
||||||
# Trae el slug desde el título si existe o una string al azar
|
# Trae el slug desde el título si existe o una string al azar
|
||||||
def default_value
|
def default_value
|
||||||
if title
|
title ? Jekyll::Utils.slugify(title) : SecureRandom.uuid
|
||||||
Jekyll::Utils.slugify(title)
|
|
||||||
else
|
|
||||||
SecureRandom.hex
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def value
|
def value
|
||||||
|
@ -40,6 +36,9 @@ class MetadataSlug < MetadataTemplate
|
||||||
|
|
||||||
# Devuelve el título a menos que sea privado y no esté vacío
|
# Devuelve el título a menos que sea privado y no esté vacío
|
||||||
def title
|
def title
|
||||||
post.title&.value&.to_s unless post.title.private? && !post.title&.value&.blank?
|
return if post.title&.private?
|
||||||
|
return if post.title&.value&.blank?
|
||||||
|
|
||||||
|
post.title&.value&.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
:value, :help, :required, :errors, :post,
|
:value, :help, :required, :errors, :post,
|
||||||
:layout, keyword_init: true) do
|
:layout, keyword_init: true) do
|
||||||
attr_reader :value_was
|
|
||||||
|
|
||||||
# Queremos que los artículos nuevos siempre cacheen, si usamos el UUID
|
# Queremos que los artículos nuevos siempre cacheen, si usamos el UUID
|
||||||
# siempre vamos a obtener un item nuevo.
|
# siempre vamos a obtener un item nuevo.
|
||||||
def cache_key
|
def cache_key
|
||||||
|
@ -25,13 +23,26 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
cache_key + '-' + cache_version
|
cache_key + '-' + cache_version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# XXX: Deberíamos sanitizar durante la asignación?
|
||||||
def value=(new_value)
|
def value=(new_value)
|
||||||
@value_was = value
|
@value_was = value
|
||||||
self[:value] = new_value
|
self[:value] = new_value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Siempre obtener el valor actual y solo obtenerlo del documento una
|
||||||
|
# vez.
|
||||||
|
def value_was
|
||||||
|
return @value_was if instance_variable_defined? '@value_was'
|
||||||
|
|
||||||
|
@value_was = value_from_document
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_from_document
|
||||||
|
@value_from_document ||= document.data[name.to_s]
|
||||||
|
end
|
||||||
|
|
||||||
def changed?
|
def changed?
|
||||||
!value_was.nil? && value_was != value
|
value_was != value
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene el valor del JekyllDocument
|
# Obtiene el valor del JekyllDocument
|
||||||
|
@ -59,7 +70,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
# Valor actual o por defecto. Al memoizarlo podemos modificarlo
|
||||||
# usando otros métodos que el de asignación.
|
# usando otros métodos que el de asignación.
|
||||||
def value
|
def value
|
||||||
self[:value] ||= if (data = document.data[name.to_s]).present?
|
self[:value] ||= if (data = value_from_document).present?
|
||||||
private? ? decrypt(data) : data
|
private? ? decrypt(data) : data
|
||||||
else
|
else
|
||||||
default_value
|
default_value
|
||||||
|
@ -112,6 +123,8 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
# En caso de que algún campo necesite realizar acciones antes de ser
|
# En caso de que algún campo necesite realizar acciones antes de ser
|
||||||
# guardado
|
# guardado
|
||||||
def save
|
def save
|
||||||
|
return true unless changed?
|
||||||
|
|
||||||
self[:value] = sanitize value
|
self[:value] = sanitize value
|
||||||
self[:value] = encrypt(value) if private?
|
self[:value] = encrypt(value) if private?
|
||||||
|
|
||||||
|
@ -123,12 +136,12 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
end
|
end
|
||||||
|
|
||||||
def related_methods
|
def related_methods
|
||||||
raise NotImplementedError
|
@related_methods ||= [].freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determina si el campo es privado y debería ser cifrado
|
# Determina si el campo es privado y debería ser cifrado
|
||||||
def private?
|
def private?
|
||||||
!!layout.metadata.dig(name, 'private')
|
layout.metadata.dig(name, 'private').present?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -172,7 +185,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
|
|
||||||
box.decrypt_str value.to_s
|
box.decrypt_str value.to_s
|
||||||
rescue Lockbox::DecryptionError => e
|
rescue Lockbox::DecryptionError => e
|
||||||
ExceptionNotifier.notify_exception(e)
|
ExceptionNotifier.notify_exception(e, data: { site: site.name, post: post.path.absolute, name: name })
|
||||||
|
|
||||||
I18n.t('lockbox.help.decryption_error')
|
I18n.t('lockbox.help.decryption_error')
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
# Esta clase representa un post en un sitio jekyll e incluye métodos
|
# Esta clase representa un post en un sitio jekyll e incluye métodos
|
||||||
# para modificarlos y crear nuevos.
|
# para modificarlos y crear nuevos.
|
||||||
#
|
#
|
||||||
# rubocop:disable Metrics/ClassLength
|
# * Los metadatos se tienen que cargar dinámicamente, solo usamos los
|
||||||
# rubocop:disable Style/MissingRespondToMissing
|
# que necesitamos
|
||||||
class Post < OpenStruct
|
#
|
||||||
|
#
|
||||||
|
class Post
|
||||||
# Atributos por defecto
|
# Atributos por defecto
|
||||||
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
DEFAULT_ATTRIBUTES = %i[site document layout].freeze
|
||||||
# Otros atributos que no vienen en los metadatos
|
# Otros atributos que no vienen en los metadatos
|
||||||
|
@ -13,6 +15,8 @@ class Post < OpenStruct
|
||||||
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
|
PUBLIC_ATTRIBUTES = %i[lang date uuid].freeze
|
||||||
ATTR_SUFFIXES = %w[? =].freeze
|
ATTR_SUFFIXES = %w[? =].freeze
|
||||||
|
|
||||||
|
attr_reader :attributes, :errors, :layout, :site, :document
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Obtiene el layout sin leer el Document
|
# Obtiene el layout sin leer el Document
|
||||||
#
|
#
|
||||||
|
@ -31,41 +35,20 @@ class Post < OpenStruct
|
||||||
#
|
#
|
||||||
def initialize(**args)
|
def initialize(**args)
|
||||||
default_attributes_missing(**args)
|
default_attributes_missing(**args)
|
||||||
super(**args)
|
|
||||||
|
|
||||||
# Genera un método con todos los atributos disponibles
|
# Genera un método con todos los atributos disponibles
|
||||||
self.attributes = layout.metadata.keys.map(&:to_sym) + PUBLIC_ATTRIBUTES
|
@layout = args[:layout]
|
||||||
self.errors = {}
|
@site = args[:site]
|
||||||
|
@document = args[:document]
|
||||||
|
@attributes = layout.attributes + PUBLIC_ATTRIBUTES
|
||||||
|
@errors = {}
|
||||||
|
@metadata = {}
|
||||||
|
|
||||||
# Genera un atributo por cada uno de los campos de la plantilla,
|
# Inicializar valores
|
||||||
# MetadataFactory devuelve un tipo de campo por cada campo. A
|
attributes.each do |attr|
|
||||||
# partir de ahí se pueden obtener los valores actuales y una lista
|
public_send(attr)&.value = args[attr] if args.key?(attr)
|
||||||
# de valores por defecto.
|
|
||||||
#
|
|
||||||
# XXX: En el primer intento de hacerlo más óptimo, movimos esta
|
|
||||||
# lógica a instanciación bajo demanda, pero no solo no logramos
|
|
||||||
# optimizar sino que aumentamos el tiempo de carga :/
|
|
||||||
layout.metadata.each_pair do |name, template|
|
|
||||||
send "#{name}=".to_sym,
|
|
||||||
MetadataFactory.build(document: document,
|
|
||||||
post: self,
|
|
||||||
site: site,
|
|
||||||
name: name.to_sym,
|
|
||||||
value: args[name.to_sym],
|
|
||||||
layout: layout,
|
|
||||||
type: template['type'],
|
|
||||||
label: template['label'],
|
|
||||||
help: template['help'],
|
|
||||||
required: template['required'])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Llamar dinámicamente
|
|
||||||
load_lang!
|
|
||||||
load_slug!
|
|
||||||
load_date!
|
|
||||||
load_path!
|
|
||||||
load_uuid!
|
|
||||||
|
|
||||||
# XXX: No usamos Post#read porque a esta altura todavía no sabemos
|
# XXX: No usamos Post#read porque a esta altura todavía no sabemos
|
||||||
# nada del Document
|
# nada del Document
|
||||||
document.read! if File.exist? document.path
|
document.read! if File.exist? document.path
|
||||||
|
@ -108,7 +91,8 @@ class Post < OpenStruct
|
||||||
html.css('img').each do |img|
|
html.css('img').each do |img|
|
||||||
next if %r{\Ahttps?://} =~ img.attributes['src']
|
next if %r{\Ahttps?://} =~ img.attributes['src']
|
||||||
|
|
||||||
img.attributes['src'].value = Rails.application.routes.url_helpers.site_static_file_url(site, file: img.attributes['src'].value)
|
img.attributes['src'].value = Rails.application.routes.url_helpers.site_static_file_url(site,
|
||||||
|
file: img.attributes['src'].value)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Notificar a les usuaries que están viendo una previsualización
|
# Notificar a les usuaries que están viendo una previsualización
|
||||||
|
@ -152,29 +136,65 @@ class Post < OpenStruct
|
||||||
@modified_at ||= Time.now
|
@modified_at ||= Time.now
|
||||||
end
|
end
|
||||||
|
|
||||||
# Solo ejecuta la magia de OpenStruct si el campo existe en la
|
def [](attr)
|
||||||
# plantilla
|
public_send attr
|
||||||
#
|
|
||||||
# XXX: Reemplazarlo por nuestro propio método, mantener todo lo demás
|
|
||||||
# compatible con OpenStruct
|
|
||||||
#
|
|
||||||
# XXX: rubocop dice que tenemos que usar super cuando ya lo estamos
|
|
||||||
# usando...
|
|
||||||
def method_missing(mid, *args)
|
|
||||||
# Limpiar el nombre del atributo, para que todos los ayudantes
|
|
||||||
# reciban el método en limpio
|
|
||||||
name = attribute_name mid
|
|
||||||
|
|
||||||
unless attribute? name
|
|
||||||
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
|
||||||
method: mid)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# OpenStruct
|
# Define metadatos a demanda
|
||||||
super(mid, *args)
|
def method_missing(name, *_args)
|
||||||
|
# Limpiar el nombre del atributo, para que todos los ayudantes
|
||||||
|
# reciban el método en limpio
|
||||||
|
unless attribute? name
|
||||||
|
raise NoMethodError, I18n.t('exceptions.post.no_method',
|
||||||
|
method: name)
|
||||||
|
end
|
||||||
|
|
||||||
# Devolver lo mismo que devuelve el método después de definirlo
|
define_singleton_method(name) do
|
||||||
send(mid, *args)
|
template = layout.metadata[name.to_s]
|
||||||
|
|
||||||
|
@metadata[name] ||=
|
||||||
|
MetadataFactory.build(document: document,
|
||||||
|
post: self,
|
||||||
|
site: site,
|
||||||
|
name: name,
|
||||||
|
layout: layout,
|
||||||
|
type: template['type'],
|
||||||
|
label: template['label'],
|
||||||
|
help: template['help'],
|
||||||
|
required: template['required'])
|
||||||
|
end
|
||||||
|
|
||||||
|
public_send name
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Mover a method_missing
|
||||||
|
def slug
|
||||||
|
@metadata[:slug] ||= MetadataSlug.new(document: document, site: site, layout: layout, name: :slug, type: :slug,
|
||||||
|
post: self, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Mover a method_missing
|
||||||
|
def date
|
||||||
|
@metadata[:date] ||= MetadataDocumentDate.new(document: document, site: site, layout: layout, name: :date,
|
||||||
|
type: :document_date, post: self, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Mover a method_missing
|
||||||
|
def path
|
||||||
|
@metadata[:path] ||= MetadataPath.new(document: document, site: site, layout: layout, name: :path, type: :path,
|
||||||
|
post: self, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Mover a method_missing
|
||||||
|
def lang
|
||||||
|
@metadata[:lang] ||= MetadataLang.new(document: document, site: site, layout: layout, name: :lang, type: :lang,
|
||||||
|
post: self, required: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Mover a method_missing
|
||||||
|
def uuid
|
||||||
|
@metadata[:uuid] ||= MetadataUuid.new(document: document, site: site, layout: layout, name: :uuid, type: :uuid,
|
||||||
|
post: self, required: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
# Detecta si es un atributo válido o no, a partir de la tabla de la
|
||||||
|
@ -189,11 +209,14 @@ class Post < OpenStruct
|
||||||
included
|
included
|
||||||
end
|
end
|
||||||
|
|
||||||
# Devuelve los strong params para el layout
|
# Devuelve los strong params para el layout.
|
||||||
|
#
|
||||||
|
# XXX: Nos gustaría no tener que instanciar Metadata acá, pero depende
|
||||||
|
# del valor por defecto que a su vez depende de Layout.
|
||||||
def params
|
def params
|
||||||
attributes.map do |attr|
|
attributes.map do |attr|
|
||||||
send(attr).to_param
|
public_send(attr)&.to_param
|
||||||
end
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
# Genera el post con metadatos en YAML
|
# Genera el post con metadatos en YAML
|
||||||
|
@ -201,8 +224,8 @@ class Post < OpenStruct
|
||||||
# TODO: Cachear por un minuto
|
# TODO: Cachear por un minuto
|
||||||
def full_content
|
def full_content
|
||||||
body = ''
|
body = ''
|
||||||
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
|
yaml = layout.attributes.map do |attr|
|
||||||
template = send(metadata)
|
template = public_send attr
|
||||||
|
|
||||||
unless template.front_matter?
|
unless template.front_matter?
|
||||||
body += "\n\n"
|
body += "\n\n"
|
||||||
|
@ -212,7 +235,7 @@ class Post < OpenStruct
|
||||||
|
|
||||||
next if template.empty?
|
next if template.empty?
|
||||||
|
|
||||||
[metadata.to_s, template.value]
|
[attr.to_s, template.value]
|
||||||
end.compact.to_h
|
end.compact.to_h
|
||||||
|
|
||||||
# TODO: Convertir a Metadata?
|
# TODO: Convertir a Metadata?
|
||||||
|
@ -281,7 +304,7 @@ class Post < OpenStruct
|
||||||
|
|
||||||
# Detecta si el artículo es válido para guardar
|
# Detecta si el artículo es válido para guardar
|
||||||
def valid?
|
def valid?
|
||||||
self.errors = {}
|
@errors = {}
|
||||||
|
|
||||||
layout.metadata.keys.map(&:to_sym).each do |metadata|
|
layout.metadata.keys.map(&:to_sym).each do |metadata|
|
||||||
template = send(metadata)
|
template = send(metadata)
|
||||||
|
@ -339,9 +362,7 @@ class Post < OpenStruct
|
||||||
# Levanta un error si al construir el artículo no pasamos un atributo.
|
# Levanta un error si al construir el artículo no pasamos un atributo.
|
||||||
def default_attributes_missing(**args)
|
def default_attributes_missing(**args)
|
||||||
DEFAULT_ATTRIBUTES.each do |attr|
|
DEFAULT_ATTRIBUTES.each do |attr|
|
||||||
i18n = I18n.t("exceptions.post.#{attr}_missing")
|
raise ArgumentError, I18n.t("exceptions.post.#{attr}_missing") unless args[attr].present?
|
||||||
|
|
||||||
raise ArgumentError, i18n unless args[attr].present?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -349,57 +370,11 @@ class Post < OpenStruct
|
||||||
document.data.fetch('usuaries', [])
|
document.data.fetch('usuaries', [])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtiene el nombre del atributo a partir del nombre del método
|
|
||||||
def attribute_name(attr)
|
|
||||||
# XXX: Los simbolos van al final
|
|
||||||
@attribute_name_cache ||= {}
|
|
||||||
@attribute_name_cache[attr] ||= ATTR_SUFFIXES.reduce(attr.to_s) do |a, suffix|
|
|
||||||
a.chomp suffix
|
|
||||||
end.to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_slug!
|
|
||||||
self.slug = MetadataSlug.new(document: document, site: site,
|
|
||||||
layout: layout, name: :slug, type: :slug,
|
|
||||||
post: self,
|
|
||||||
required: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_date!
|
|
||||||
self.date = MetadataDocumentDate.new(document: document, site: site,
|
|
||||||
layout: layout, name: :date,
|
|
||||||
type: :document_date,
|
|
||||||
post: self,
|
|
||||||
required: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_path!
|
|
||||||
self.path = MetadataPath.new(document: document, site: site,
|
|
||||||
layout: layout, name: :path,
|
|
||||||
type: :path, post: self,
|
|
||||||
required: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_lang!
|
|
||||||
self.lang = MetadataLang.new(document: document, site: site,
|
|
||||||
layout: layout, name: :lang,
|
|
||||||
type: :lang, post: self,
|
|
||||||
required: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_uuid!
|
|
||||||
self.uuid = MetadataUuid.new(document: document, site: site,
|
|
||||||
layout: layout, name: :uuid,
|
|
||||||
type: :uuid, post: self,
|
|
||||||
required: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ejecuta la acción de guardado en cada atributo
|
# Ejecuta la acción de guardado en cada atributo
|
||||||
|
# TODO: Solo guardar los que se modificaron
|
||||||
def save_attributes!
|
def save_attributes!
|
||||||
attributes.map do |attr|
|
attributes.map do |attr|
|
||||||
send(attr).save
|
public_send(attr).save
|
||||||
end.all?
|
end.all?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ClassLength
|
|
||||||
# rubocop:enable Style/MissingRespondToMissing
|
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
# artículos como si estuviésemos usando ActiveRecord.
|
# artículos como si estuviésemos usando ActiveRecord.
|
||||||
class PostRelation < Array
|
class PostRelation < Array
|
||||||
# No necesitamos cambiar el sitio
|
# No necesitamos cambiar el sitio
|
||||||
attr_reader :site
|
attr_reader :site, :lang
|
||||||
|
|
||||||
def initialize(site:)
|
def initialize(site:, lang:)
|
||||||
@site = site
|
@site = site
|
||||||
|
@lang = lang
|
||||||
# Proseguimos la inicialización sin valores por defecto
|
# Proseguimos la inicialización sin valores por defecto
|
||||||
super()
|
super()
|
||||||
end
|
end
|
||||||
|
@ -15,7 +16,7 @@ class PostRelation < Array
|
||||||
# Genera un artículo nuevo con los parámetros que le pasemos y lo suma
|
# Genera un artículo nuevo con los parámetros que le pasemos y lo suma
|
||||||
# al array
|
# al array
|
||||||
def build(**args)
|
def build(**args)
|
||||||
args[:lang] ||= I18n.locale
|
args[:lang] = lang
|
||||||
args[:document] ||= build_document(collection: args[:lang])
|
args[:document] ||= build_document(collection: args[:lang])
|
||||||
args[:layout] = build_layout(args[:layout])
|
args[:layout] = build_layout(args[:layout])
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ class PostRelation < Array
|
||||||
|
|
||||||
@where ||= {}
|
@where ||= {}
|
||||||
@where[args.hash.to_s] ||= begin
|
@where[args.hash.to_s] ||= begin
|
||||||
PostRelation.new(site: site).concat(select do |post|
|
PostRelation.new(site: site, lang: lang).concat(select do |post|
|
||||||
result = args.map do |attr, value|
|
result = args.map do |attr, value|
|
||||||
next unless post.attribute?(attr)
|
next unless post.attribute?(attr)
|
||||||
|
|
||||||
|
@ -123,7 +124,7 @@ class PostRelation < Array
|
||||||
# @return [PostRelation]
|
# @return [PostRelation]
|
||||||
alias array_select select
|
alias array_select select
|
||||||
def select(&block)
|
def select(&block)
|
||||||
PostRelation.new(site: site).concat array_select(&block)
|
PostRelation.new(site: site, lang: lang).concat array_select(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Intenta guardar todos y devuelve true si pudo
|
# Intenta guardar todos y devuelve true si pudo
|
||||||
|
|
|
@ -225,6 +225,7 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
# Traemos los posts del idioma actual por defecto
|
# Traemos los posts del idioma actual por defecto
|
||||||
lang ||= I18n.locale
|
lang ||= I18n.locale
|
||||||
|
lang = lang.to_sym
|
||||||
|
|
||||||
# Crea un Struct dinámico con los valores de los locales, si
|
# Crea un Struct dinámico con los valores de los locales, si
|
||||||
# llegamos a pasar un idioma que no existe vamos a tener una
|
# llegamos a pasar un idioma que no existe vamos a tener una
|
||||||
|
@ -233,7 +234,7 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
return @posts[lang] unless @posts[lang].blank?
|
return @posts[lang] unless @posts[lang].blank?
|
||||||
|
|
||||||
@posts[lang] = PostRelation.new site: self
|
@posts[lang] = PostRelation.new site: self, lang: lang
|
||||||
|
|
||||||
# No fallar si no existe colección para este idioma
|
# No fallar si no existe colección para este idioma
|
||||||
# XXX: queremos fallar silenciosamente?
|
# XXX: queremos fallar silenciosamente?
|
||||||
|
@ -250,7 +251,7 @@ class Site < ApplicationRecord
|
||||||
#
|
#
|
||||||
# @return PostRelation
|
# @return PostRelation
|
||||||
def docs
|
def docs
|
||||||
@docs ||= PostRelation.new(site: self).push(locales.flat_map do |locale|
|
@docs ||= PostRelation.new(site: self, lang: :docs).push(locales.flat_map do |locale|
|
||||||
posts(lang: locale)
|
posts(lang: locale)
|
||||||
end).flatten!
|
end).flatten!
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,6 +42,7 @@ class Site
|
||||||
metadata = doc.public_send(field)
|
metadata = doc.public_send(field)
|
||||||
|
|
||||||
next if metadata.value['path'].blank?
|
next if metadata.value['path'].blank?
|
||||||
|
next if ActiveStorage::Blob.find_by(key: metadata.key_from_path)
|
||||||
|
|
||||||
path = Pathname.new(metadata.value['path'])
|
path = Pathname.new(metadata.value['path'])
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,8 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? usuarie
|
params[:post][:draft] = true if site.invitade? usuarie
|
||||||
|
|
||||||
|
# Es importante que el artículo se guarde primero y luego los
|
||||||
|
# relacionados.
|
||||||
commit(action: :updated, file: update_related_posts) if post.update(post_params)
|
commit(action: :updated, file: update_related_posts) if post.update(post_params)
|
||||||
|
|
||||||
# Devolver el post aunque no se haya salvado para poder rescatar los
|
# Devolver el post aunque no se haya salvado para poder rescatar los
|
||||||
|
@ -111,23 +113,24 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
# Actualiza los artículos relacionados según los métodos que los
|
# Actualiza los artículos relacionados según los métodos que los
|
||||||
# metadatos declaren.
|
# metadatos declaren.
|
||||||
#
|
#
|
||||||
|
# Este método se asegura que todos los artículos se guardan una sola
|
||||||
|
# vez.
|
||||||
|
#
|
||||||
# @return [Array] Lista de archivos modificados
|
# @return [Array] Lista de archivos modificados
|
||||||
def update_related_posts
|
def update_related_posts
|
||||||
files = [post.path.absolute]
|
posts = Set.new
|
||||||
|
|
||||||
post.attributes.each do |a|
|
post.attributes.each do |a|
|
||||||
next unless post[a].related_posts?
|
|
||||||
|
|
||||||
post[a].related_methods.each do |m|
|
post[a].related_methods.each do |m|
|
||||||
next unless post[a].respond_to? m
|
next unless post[a].respond_to? m
|
||||||
|
|
||||||
# La respuesta puede ser una PostRelation también
|
# La respuesta puede ser una PostRelation también
|
||||||
[post[a].public_send(m)].flatten.compact.uniq.each do |p|
|
posts.merge [post[a].public_send(m)].flatten.compact
|
||||||
files << p.path.absolute if p.save(validate: false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
files
|
posts.map do |p|
|
||||||
|
p.path.absolute if p.save(validate: false)
|
||||||
|
end.compact << post.path.absolute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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.belongs_to.each do |p|
|
- metadata.has_many.each do |p|
|
||||||
%li= link_to p.title.value, site_post_path(site, p.id)
|
%li= link_to p.title.value, site_post_path(site, p.id)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
|
= hidden_field_tag "#{base}[#{attribute}][]", ''
|
||||||
|
|
||||||
.taggable{ dir: dir, lang: locale, data: { values: metadata.value.to_json,
|
.taggable{ dir: dir, lang: locale, data: { values: metadata.value.to_json,
|
||||||
name: "#{base}[#{attribute}][]", list: id_for_datalist(attribute),
|
name: "#{base}[#{attribute}][]", list: id_for_datalist(attribute),
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
= text_field base, attribute, value: metadata.value,
|
= select_tag(plain_field_name_for(base, attribute),
|
||||||
dir: dir, lang: locale, list: id_for_datalist(attribute),
|
options_for_select(metadata.values, metadata.value),
|
||||||
pattern: metadata.values.values.join('|'), autocomplete: 'off',
|
**field_options(attribute, metadata), include_blank: true)
|
||||||
**field_options(attribute, metadata)
|
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: attribute, metadata: metadata
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
||||||
-# TODO: Ocultar el UUID
|
|
||||||
%datalist{ id: id_for_datalist(attribute) }
|
|
||||||
- metadata.values.each_pair do |key, value|
|
|
||||||
%option{ value: value }= key
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
= date_field base, attribute, value: metadata.value.to_date.strftime('%F'),
|
= date_field base, attribute, value: metadata.value.to_date.strftime('%F'),
|
||||||
**field_options(attribute, metadata)
|
**field_options(attribute, metadata), pattern: '\d{4}-\d{2}-\d{2}',
|
||||||
|
data: { 'pattern-mismatch': t('metadata.date.invalid_format') }
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: attribute, metadata: metadata
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
= date_field base, attribute, value: metadata.value.strftime('%F'),
|
= date_field base, attribute, value: metadata.value.strftime('%F'),
|
||||||
**field_options(attribute, metadata)
|
**field_options(attribute, metadata), pattern: '\d{4}-\d{2}-\d{2}',
|
||||||
|
data: { 'pattern-mismatch': t('metadata.date.invalid_format') }
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: attribute, metadata: metadata
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
|
@ -34,6 +34,6 @@
|
||||||
= text_field(*field_name_for(base, attribute, :description),
|
= text_field(*field_name_for(base, attribute, :description),
|
||||||
value: metadata.value['description'],
|
value: metadata.value['description'],
|
||||||
dir: dir, lang: locale,
|
dir: dir, lang: locale,
|
||||||
**field_options(attribute, metadata))
|
**field_options(attribute, metadata, required: false))
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: [attribute, :description], metadata: metadata
|
post: post, attribute: [attribute, :description], metadata: metadata
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
|
= hidden_field_tag "#{base}[#{attribute}][]", ''
|
||||||
|
|
||||||
.mapable{ dir: dir, lang: locale,
|
.mapable{ dir: dir, lang: locale,
|
||||||
data: { values: metadata.value.to_json,
|
data: { values: metadata.value.to_json,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.form-group
|
.form-group
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
||||||
|
= hidden_field_tag "#{base}[#{attribute}][]", ''
|
||||||
|
|
||||||
.mapable{ dir: dir, lang: locale,
|
.mapable{ dir: dir, lang: locale,
|
||||||
data: { values: metadata.value.to_json,
|
data: { values: metadata.value.to_json,
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
= text_field(*field_name_for(base, attribute, :description),
|
= text_field(*field_name_for(base, attribute, :description),
|
||||||
value: metadata.value['description'],
|
value: metadata.value['description'],
|
||||||
dir: dir, lang: locale,
|
dir: dir, lang: locale,
|
||||||
**field_options(attribute, metadata))
|
**field_options(attribute, metadata, required: false))
|
||||||
= render 'posts/attribute_feedback',
|
= render 'posts/attribute_feedback',
|
||||||
post: post, attribute: [attribute, :description], metadata: metadata
|
post: post, attribute: [attribute, :description], metadata: metadata
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ en:
|
||||||
end_in_the_past: "Event end can't happen before the start"
|
end_in_the_past: "Event end can't happen before the start"
|
||||||
belongs_to:
|
belongs_to:
|
||||||
missing_post: "Couldn't find the related post"
|
missing_post: "Couldn't find the related post"
|
||||||
|
has_many:
|
||||||
|
missing_posts: "Couldn't find some related posts"
|
||||||
|
date:
|
||||||
|
invalid_format: "It seems that your browser doesn't support dates and the date is on the incorrect format, please use yyyy-mm-dd, ie. 2021-01-31"
|
||||||
exceptions:
|
exceptions:
|
||||||
post:
|
post:
|
||||||
site_missing: 'Needs an instance of Site'
|
site_missing: 'Needs an instance of Site'
|
||||||
|
@ -460,7 +464,7 @@ en:
|
||||||
label: Language
|
label: Language
|
||||||
date:
|
date:
|
||||||
label: Date
|
label: Date
|
||||||
help: Publication date for this post. If you use a date in the future the post won't be published until then.
|
help: Date for this post. If you use a date in the future the post won't be published until you publish changes on that day.
|
||||||
required:
|
required:
|
||||||
label: ' (required)'
|
label: ' (required)'
|
||||||
feedback: 'This field cannot be empty!'
|
feedback: 'This field cannot be empty!'
|
||||||
|
|
|
@ -48,6 +48,10 @@ es:
|
||||||
end_in_the_past: 'El fin del evento no puede ser anterior al comienzo'
|
end_in_the_past: 'El fin del evento no puede ser anterior al comienzo'
|
||||||
belongs_to:
|
belongs_to:
|
||||||
missing_post: 'No se pudo encontrar el artículo relacionado'
|
missing_post: 'No se pudo encontrar el artículo relacionado'
|
||||||
|
has_many:
|
||||||
|
missing_posts: 'No se pudieron encontrar algunos artículos relacionados'
|
||||||
|
date:
|
||||||
|
invalid_format: 'Parece que tu navegador no soporta fechas y la fecha no está en el formato correcto, por favor usa aaaa-mm-dd, por ejemplo: 2021-01-31'
|
||||||
exceptions:
|
exceptions:
|
||||||
post:
|
post:
|
||||||
site_missing: 'Necesita una instancia de Site'
|
site_missing: 'Necesita una instancia de Site'
|
||||||
|
@ -469,7 +473,7 @@ es:
|
||||||
label: Idioma
|
label: Idioma
|
||||||
date:
|
date:
|
||||||
label: Fecha
|
label: Fecha
|
||||||
help: La fecha de publicación del artículo. Si colocas una fecha en el futuro no se publicará hasta ese día.
|
help: La fecha del artículo. Si colocas una fecha en el futuro no se publicará hasta que publiques cambios ese día.
|
||||||
required:
|
required:
|
||||||
label: ' (requerido)'
|
label: ' (requerido)'
|
||||||
feedback: '¡Este campo no puede estar vacío!'
|
feedback: '¡Este campo no puede estar vacío!'
|
||||||
|
|
|
@ -143,21 +143,26 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'se pueden reordenar' do
|
test 'se pueden reordenar' do
|
||||||
lang = I18n.available_locales.sample
|
lang = { lang: @site.locales.sample }
|
||||||
posts = @site.posts(lang: lang)
|
|
||||||
|
(rand * 10).round.times do
|
||||||
|
@site.posts(**lang).create title: SecureRandom.hex, description: SecureRandom.hex
|
||||||
|
end
|
||||||
|
|
||||||
|
posts = @site.posts(**lang)
|
||||||
reorder = Hash[posts.map { |p| p.uuid.value }.shuffle.each_with_index.to_a]
|
reorder = Hash[posts.map { |p| p.uuid.value }.shuffle.each_with_index.to_a]
|
||||||
|
|
||||||
post site_posts_reorder_url(@site),
|
post site_posts_reorder_url(@site),
|
||||||
headers: @authorization,
|
headers: @authorization,
|
||||||
params: { post: { lang: lang, reorder: reorder } }
|
params: { post: { lang: lang[:lang], reorder: reorder } }
|
||||||
|
|
||||||
@site = Site.find @site.id
|
@site = Site.find @site.id
|
||||||
|
|
||||||
assert_equal I18n.t('post_service.reorder'),
|
|
||||||
@site.repository.rugged.head.target.message
|
|
||||||
assert_equal reorder,
|
assert_equal reorder,
|
||||||
Hash[@site.posts(lang: lang).map do |p|
|
Hash[@site.posts(**lang).map do |p|
|
||||||
[p.uuid.value, p.order.value]
|
[p.uuid.value, p.order.value]
|
||||||
end]
|
end]
|
||||||
|
assert_equal I18n.t('post_service.reorder'),
|
||||||
|
@site.repository.rugged.head.target.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,11 +95,6 @@ class PostTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'attribute_name' do
|
|
||||||
assert_equal :hola, @post.send(:attribute_name, :hola)
|
|
||||||
assert_equal :hola, @post.send(:attribute_name, :hola?)
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'se puede cambiar el slug' do
|
test 'se puede cambiar el slug' do
|
||||||
@post.title.value = SecureRandom.hex
|
@post.title.value = SecureRandom.hex
|
||||||
assert_not @post.slug.changed?
|
assert_not @post.slug.changed?
|
||||||
|
|
Loading…
Reference in a new issue