mirror of
https://0xacab.org/sutty/sutty
synced 2025-03-01 05:31:48 +00:00
Merge branch 'production.panel.sutty.nl' of 0xacab.org:sutty/sutty into production.panel.sutty.nl
This commit is contained in:
commit
6a3bdb7054
38 changed files with 353 additions and 108 deletions
|
@ -5,9 +5,10 @@
|
||||||
# No necesitamos autenticación aun
|
# No necesitamos autenticación aun
|
||||||
class CollaborationsController < ApplicationController
|
class CollaborationsController < ApplicationController
|
||||||
include Pundit
|
include Pundit
|
||||||
|
include StrongParamsHelper
|
||||||
|
|
||||||
def collaborate
|
def collaborate
|
||||||
@site = Site.find_by_name(params[:site_id])
|
@site = Site.find_by_name(pluck_param(:site_id))
|
||||||
authorize Collaboration.new(@site)
|
authorize Collaboration.new(@site)
|
||||||
|
|
||||||
@invitade = current_usuarie || @site.usuaries.build
|
@invitade = current_usuarie || @site.usuaries.build
|
||||||
|
@ -21,7 +22,7 @@ class CollaborationsController < ApplicationController
|
||||||
#
|
#
|
||||||
# * Si le usuarie existe y no está logueade, pedirle la contraseña
|
# * Si le usuarie existe y no está logueade, pedirle la contraseña
|
||||||
def accept_collaboration
|
def accept_collaboration
|
||||||
@site = Site.find_by_name(params[:site_id])
|
@site = Site.find_by_name(pluck_param(:site_id))
|
||||||
authorize Collaboration.new(@site)
|
authorize Collaboration.new(@site)
|
||||||
|
|
||||||
@invitade = current_usuarie
|
@invitade = current_usuarie
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
# Controlador para artículos
|
# Controlador para artículos
|
||||||
class PostsController < ApplicationController
|
class PostsController < ApplicationController
|
||||||
|
include StrongParamsHelper
|
||||||
|
|
||||||
before_action :authenticate_usuarie!
|
before_action :authenticate_usuarie!
|
||||||
before_action :service_for_direct_upload, only: %i[new edit]
|
before_action :service_for_direct_upload, only: %i[new edit]
|
||||||
|
|
||||||
|
@ -17,9 +19,9 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
# @todo Mover a tu propio scope
|
# @todo Mover a tu propio scope
|
||||||
def new_array
|
def new_array
|
||||||
@value = params.require(:value).strip
|
@value = pluck_param(:value)
|
||||||
@name = params.require(:name).strip
|
@name = pluck_param(:name)
|
||||||
id = params.require(:id).strip
|
id = pluck_param(:id)
|
||||||
|
|
||||||
headers['HX-Trigger-After-Swap'] = 'htmx:resetForm'
|
headers['HX-Trigger-After-Swap'] = 'htmx:resetForm'
|
||||||
|
|
||||||
|
@ -27,13 +29,13 @@ class PostsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_array_value
|
def new_array_value
|
||||||
@value = params.require(:value).strip
|
@value = pluck_param(:value)
|
||||||
|
|
||||||
render layout: false
|
render layout: false
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_related_post
|
def new_related_post
|
||||||
@uuid = params.require(:value).strip
|
@uuid = pluck_param(:value)
|
||||||
|
|
||||||
@indexed_post = site.indexed_posts.find_by!(post_id: @uuid)
|
@indexed_post = site.indexed_posts.find_by!(post_id: @uuid)
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ class PostsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_has_one
|
def new_has_one
|
||||||
@uuid = params.require(:value).strip
|
@uuid = pluck_param(:value)
|
||||||
|
|
||||||
@indexed_post = site.indexed_posts.find_by!(post_id: @uuid)
|
@indexed_post = site.indexed_posts.find_by!(post_id: @uuid)
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ class PostsController < ApplicationController
|
||||||
# El formulario de un Post, si pasamos el UUID, estamos editando, sino
|
# El formulario de un Post, si pasamos el UUID, estamos editando, sino
|
||||||
# estamos creando.
|
# estamos creando.
|
||||||
def form
|
def form
|
||||||
uuid = params.permit(:uuid).try(:[], :uuid).presence
|
uuid = pluck_param(:uuid, optional: true)
|
||||||
locale
|
locale
|
||||||
|
|
||||||
@post =
|
@post =
|
||||||
|
@ -59,7 +61,31 @@ class PostsController < ApplicationController
|
||||||
site.indexed_posts.find_by!(post_id: uuid).post
|
site.indexed_posts.find_by!(post_id: uuid).post
|
||||||
else
|
else
|
||||||
# @todo Usar la base de datos
|
# @todo Usar la base de datos
|
||||||
site.posts(lang: locale).build(layout: params.require(:layout))
|
site.posts(lang: locale).build(layout: pluck_param(:layout))
|
||||||
|
end
|
||||||
|
|
||||||
|
swap_modals
|
||||||
|
|
||||||
|
render layout: false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Genera un modal completo
|
||||||
|
#
|
||||||
|
# @todo recibir el atributo anterior
|
||||||
|
# @param :uuid [String] UUID del post (opcional)
|
||||||
|
# @param :layout [String] El layout a cargar (opcional)
|
||||||
|
def modal
|
||||||
|
uuid = pluck_param(:uuid, optional: true)
|
||||||
|
locale
|
||||||
|
|
||||||
|
# @todo hacer que si el uuid no existe se genera un post, para poder
|
||||||
|
# pasar el uuid sabiendolo
|
||||||
|
@post =
|
||||||
|
if uuid.present?
|
||||||
|
site.indexed_posts.find_by!(post_id: uuid).post
|
||||||
|
else
|
||||||
|
# @todo Usar la base de datos
|
||||||
|
site.posts(lang: locale).build(layout: pluck_param(:layout))
|
||||||
end
|
end
|
||||||
|
|
||||||
swap_modals
|
swap_modals
|
||||||
|
@ -107,7 +133,7 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
def new
|
def new
|
||||||
authorize Post
|
authorize Post
|
||||||
@post = site.posts(lang: locale).build(layout: params[:layout])
|
@post = site.posts(lang: locale).build(layout: pluck_param(:layout))
|
||||||
|
|
||||||
breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), ''
|
breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), ''
|
||||||
end
|
end
|
||||||
|
@ -128,17 +154,17 @@ class PostsController < ApplicationController
|
||||||
# condiciones.
|
# condiciones.
|
||||||
if htmx?
|
if htmx?
|
||||||
if post.persisted?
|
if post.persisted?
|
||||||
triggers = { 'notification:show' => { 'id' => params.permit(:saved).values.first } }
|
triggers = { 'notification:show' => { 'id' => pluck_param(:saved, optional: true) } }
|
||||||
|
|
||||||
swap_modals(triggers)
|
swap_modals(triggers)
|
||||||
|
|
||||||
@value = post.title.value
|
@value = post.title.value
|
||||||
@uuid = post.uuid.value
|
@uuid = post.uuid.value
|
||||||
@name = params.require(:name)
|
@name = pluck_param(:name)
|
||||||
|
|
||||||
render render_path_from_attribute, layout: false
|
render render_path_from_attribute, layout: false
|
||||||
else
|
else
|
||||||
headers['HX-Retarget'] = "##{params.require(:form)}"
|
headers['HX-Retarget'] = "##{pluck_param(:form)}"
|
||||||
headers['HX-Reswap'] = 'outerHTML'
|
headers['HX-Reswap'] = 'outerHTML'
|
||||||
|
|
||||||
render 'posts/form', layout: false, post: post, site: site, **params.permit(:form, :base, :dir, :locale)
|
render 'posts/form', layout: false, post: post, site: site, **params.permit(:form, :base, :dir, :locale)
|
||||||
|
@ -156,6 +182,16 @@ class PostsController < ApplicationController
|
||||||
breadcrumb 'posts.edit', ''
|
breadcrumb 'posts.edit', ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Este endpoint se encarga de actualizar el post. Si el post se edita
|
||||||
|
# desde el formulario principal, re-renderizamos el formulario si hay
|
||||||
|
# errores o enviamos a otro lado al guardar.
|
||||||
|
#
|
||||||
|
# Si los datos llegaron por HTMX, hay que regenerar el formulario
|
||||||
|
# y reemplazarlo en su modal (?) o responder con su tarjeta para
|
||||||
|
# reemplazarla donde sea que esté.
|
||||||
|
#
|
||||||
|
# @todo la re-renderización del formulario no es necesaria si tenemos
|
||||||
|
# validación client-side.
|
||||||
def update
|
def update
|
||||||
authorize post
|
authorize post
|
||||||
|
|
||||||
|
@ -171,15 +207,26 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
if htmx?
|
if htmx?
|
||||||
if post.persisted?
|
if post.persisted?
|
||||||
triggers = { 'notification:show' => params.permit(:saved).values.first }
|
triggers = { 'notification:show' => pluck_param(:saved, optional: true) }
|
||||||
|
|
||||||
swap_modals(triggers)
|
swap_modals(triggers)
|
||||||
|
|
||||||
@value = post.title.value
|
@value = post.title.value
|
||||||
@uuid = post.uuid.value
|
@uuid = post.uuid.value
|
||||||
@name = params.require(:name)
|
|
||||||
|
if (result_id = pluck_param(:result_id, optional: true))
|
||||||
|
headers['HX-Retarget'] = "##{result_id}"
|
||||||
|
headers['HX-Reswap'] = 'outerHTML'
|
||||||
|
|
||||||
|
@indexed_post = site.indexed_posts.find_by_post_id(post.uuid.value)
|
||||||
|
|
||||||
|
render 'posts/new_related_post', layout: false
|
||||||
|
# @todo Confirmar que esta ruta no esté transitada
|
||||||
|
else
|
||||||
|
@name = pluck_param(:name)
|
||||||
|
|
||||||
render render_path_from_attribute, layout: false
|
render render_path_from_attribute, layout: false
|
||||||
|
end
|
||||||
else
|
else
|
||||||
headers['HX-Retarget'] = "##{params.require(:form)}"
|
headers['HX-Retarget'] = "##{params.require(:form)}"
|
||||||
headers['HX-Reswap'] = 'outerHTML'
|
headers['HX-Reswap'] = 'outerHTML'
|
||||||
|
@ -272,7 +319,7 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def render_path_from_attribute
|
def render_path_from_attribute
|
||||||
case params.require(:attribute)
|
case pluck_param(:attribute)
|
||||||
when 'new_has_many' then 'posts/new_has_many_value'
|
when 'new_has_many' then 'posts/new_has_many_value'
|
||||||
when 'new_belongs_to' then 'posts/new_belongs_to_value'
|
when 'new_belongs_to' then 'posts/new_belongs_to_value'
|
||||||
when 'new_has_and_belongs_to_many' then 'posts/new_has_many_value'
|
when 'new_has_and_belongs_to_many' then 'posts/new_has_many_value'
|
||||||
|
|
|
@ -155,9 +155,17 @@ module ApplicationHelper
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Obtiene la traducción desde el esquema en el idioma actual, o por
|
||||||
|
# defecto en el idioma del sitio. De lo contrario trae una traducción
|
||||||
|
# genérica.
|
||||||
|
#
|
||||||
|
# Si el idioma por defecto tiene un String vacía, se asume que no
|
||||||
|
# texto.
|
||||||
|
#
|
||||||
|
# @return [String,nil]
|
||||||
def post_t(*attribute, post:, type:)
|
def post_t(*attribute, post:, type:)
|
||||||
post.layout.metadata.dig(*attribute, type.to_s, I18n.locale.to_s).presence ||
|
post.layout.metadata.dig(*attribute, type.to_s, I18n.locale.to_s).presence ||
|
||||||
post.layout.metadata.dig(*attribute, type.to_s, post.site.default_locale.to_s).presence ||
|
post.layout.metadata.dig(*attribute, type.to_s, post.site.default_locale.to_s) ||
|
||||||
I18n.t("posts.attributes.#{attribute.join('.')}.#{type}").presence
|
I18n.t("posts.attributes.#{attribute.join('.')}.#{type}").presence
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
19
app/helpers/strong_params_helper.rb
Normal file
19
app/helpers/strong_params_helper.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Métodos reutilizables para trabajar con StrongParams
|
||||||
|
module StrongParamsHelper
|
||||||
|
|
||||||
|
# Obtiene el valor de un param
|
||||||
|
#
|
||||||
|
# @todo No hay una forma mejor de hacer esto?
|
||||||
|
# @param param [Symbol]
|
||||||
|
# @param :optional [Bool]
|
||||||
|
# @return [nil,String]
|
||||||
|
def pluck_param(param, optional: false)
|
||||||
|
if optional
|
||||||
|
params.permit(param).values.first.presence
|
||||||
|
else
|
||||||
|
params.require(param).presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -103,6 +103,8 @@ export default class extends Controller {
|
||||||
this.currentTarget.innerHTML = "";
|
this.currentTarget.innerHTML = "";
|
||||||
this.originalValue = [];
|
this.originalValue = [];
|
||||||
|
|
||||||
|
const signal = window.abortController?.signal;
|
||||||
|
|
||||||
for (const itemTarget of this.itemTargets) {
|
for (const itemTarget of this.itemTargets) {
|
||||||
if (!itemTarget.dataset.value) continue;
|
if (!itemTarget.dataset.value) continue;
|
||||||
if (!this.isChecked(itemTarget)) continue;
|
if (!this.isChecked(itemTarget)) continue;
|
||||||
|
@ -114,8 +116,7 @@ export default class extends Controller {
|
||||||
|
|
||||||
this.currentTarget.appendChild(placeholder);
|
this.currentTarget.appendChild(placeholder);
|
||||||
|
|
||||||
// TODO: Renderizarlas todas juntas
|
fetch(this.newArrayValueURL, { signal })
|
||||||
fetch(this.newArrayValueURL)
|
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((body) => {
|
.then((body) => {
|
||||||
const template = document.createElement("template");
|
const template = document.createElement("template");
|
||||||
|
|
107
app/javascript/controllers/htmx_controller.js
Normal file
107
app/javascript/controllers/htmx_controller.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import { Controller } from "stimulus";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un controlador que imita a HTMX
|
||||||
|
*/
|
||||||
|
export default class extends Controller {
|
||||||
|
connect() {
|
||||||
|
// @todo Convertir en <template>
|
||||||
|
this.placeholder = "<span class=\"placeholder w-100\" aria-hidden=\"true\"></span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtiene la URL y elimina la acción.
|
||||||
|
*
|
||||||
|
* @param event [Event]
|
||||||
|
*/
|
||||||
|
getUrlOnce(event) {
|
||||||
|
this.getUrl(event);
|
||||||
|
|
||||||
|
event.target.dataset.action = event.target.dataset.action.replace("htmx#getUrlOnce", "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lanza el evento que va a descargar la URL y agregarse en algún
|
||||||
|
* lado.
|
||||||
|
*
|
||||||
|
* @param event [Event]
|
||||||
|
*/
|
||||||
|
getUrl(event) {
|
||||||
|
// @todo Stimulus >1
|
||||||
|
const value = event.target.dataset.htmxGetUrlParam;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
window.dispatchEvent(new CustomEvent("htmx:getUrl", { detail: { value } }));
|
||||||
|
} else {
|
||||||
|
console.error("Missing data-htmx-get-url-param attribute on element", event.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Realiza una petición.
|
||||||
|
*
|
||||||
|
* @param url [String]
|
||||||
|
* @return [Promise<Response>]
|
||||||
|
*/
|
||||||
|
async request(url) {
|
||||||
|
const headers = new Headers();
|
||||||
|
const signal = window.abortController?.signal;
|
||||||
|
|
||||||
|
headers.set("HX-Request", "true");
|
||||||
|
|
||||||
|
return fetch(url, { headers, signal });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtiene la URL enviada por el evento y reemplaza el contenido del
|
||||||
|
* elemento.
|
||||||
|
*/
|
||||||
|
async swap(event) {
|
||||||
|
const response = await this.request(event.detail.value);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.element.innerHTML = this.placeholder;
|
||||||
|
this.element.innerHTML = await response.text();
|
||||||
|
this.triggerEvents(response.headers);
|
||||||
|
window.htmx.process(this.element);
|
||||||
|
} else {
|
||||||
|
console.error(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Agrega el resultado de la descarga al final del elemento.
|
||||||
|
*/
|
||||||
|
async beforeend(event) {
|
||||||
|
const response = await this.request(event.detail.value);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.element.insertAdjacentHTML("beforeend", this.placeholder);
|
||||||
|
this.element.lastElementChild.outerHTML = await response.text();
|
||||||
|
|
||||||
|
this.triggerEvents(response.headers);
|
||||||
|
|
||||||
|
// @todo Asume que cada endpoint solo devuelve un elemento por vez
|
||||||
|
window.htmx.process(this.element.lastElementChild);
|
||||||
|
} else {
|
||||||
|
console.error(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lanza los eventos que vienen con la respuesta.
|
||||||
|
*/
|
||||||
|
triggerEvents(headers) {
|
||||||
|
if (!headers.has("HX-Trigger")) return;
|
||||||
|
|
||||||
|
const events = JSON.parse(headers.get("HX-Trigger"));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
for (const event in events) {
|
||||||
|
const detail = events[event];
|
||||||
|
|
||||||
|
window.dispatchEvent(new CustomEvent(event, { detail }));
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
%hr/
|
%hr/
|
||||||
|
|
||||||
- locale = params.permit(:locale)
|
- locale = pluck_param(:locale, optional: true)
|
||||||
|
|
||||||
- if controller_name != 'sessions'
|
- if controller_name != 'sessions'
|
||||||
= link_to t('.sign_in'), new_session_path(resource_name, params: locale),
|
= link_to t('.sign_in'), new_session_path(resource_name, params: locale),
|
||||||
|
|
19
app/views/posts/_errors.haml
Normal file
19
app/views/posts/_errors.haml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
- unless post.errors.empty?
|
||||||
|
- title = t('.errors.title')
|
||||||
|
- help = t('.errors.help')
|
||||||
|
= render 'bootstrap/alert' do
|
||||||
|
%h4= title
|
||||||
|
%p= help
|
||||||
|
|
||||||
|
%ul
|
||||||
|
- post.errors.each do |attribute, errors|
|
||||||
|
- if errors.size > 1
|
||||||
|
%li
|
||||||
|
%strong= post_label_t attribute, post: post
|
||||||
|
%ul
|
||||||
|
- errors.each do |error|
|
||||||
|
%li= error
|
||||||
|
- else
|
||||||
|
%li
|
||||||
|
%strong= post_label_t attribute, post: post
|
||||||
|
= errors.first
|
|
@ -1,22 +1,4 @@
|
||||||
- unless post.errors.empty?
|
= render 'errors', post: post
|
||||||
- title = t('.errors.title')
|
|
||||||
- help = t('.errors.help')
|
|
||||||
= render 'bootstrap/alert' do
|
|
||||||
%h4= title
|
|
||||||
%p= help
|
|
||||||
|
|
||||||
%ul
|
|
||||||
- post.errors.each do |attribute, errors|
|
|
||||||
- if errors.size > 1
|
|
||||||
%li
|
|
||||||
%strong= post_label_t attribute, post: post
|
|
||||||
%ul
|
|
||||||
- errors.each do |error|
|
|
||||||
%li= error
|
|
||||||
- else
|
|
||||||
%li
|
|
||||||
%strong= post_label_t attribute, post: post
|
|
||||||
= errors.first
|
|
||||||
|
|
||||||
-# TODO: habilitar form_for
|
-# TODO: habilitar form_for
|
||||||
:ruby
|
:ruby
|
||||||
|
@ -55,3 +37,11 @@
|
||||||
|
|
||||||
-# Formularios usados por los modales
|
-# Formularios usados por los modales
|
||||||
= yield(:post_form)
|
= yield(:post_form)
|
||||||
|
|
||||||
|
-#
|
||||||
|
Acumulador de formularios dinámicos, se van cargando a medida que se
|
||||||
|
necesitan en lugar de recursivamente.
|
||||||
|
|
||||||
|
Nunca se eliminan los modales una vez que se cargan para poder tener
|
||||||
|
historial de cambios.
|
||||||
|
%div{ data: { controller: 'htmx', action: 'htmx:getUrl@window->htmx#beforeend' } }
|
||||||
|
|
|
@ -18,22 +18,22 @@
|
||||||
:ruby
|
:ruby
|
||||||
except = %i[date]
|
except = %i[date]
|
||||||
|
|
||||||
if (inverse = params.permit(:inverse).try(:[], :inverse).presence)
|
if (inverse = pluck_param(:inverse, optional: true))
|
||||||
except << inverse.to_sym
|
except << inverse.to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
id: params.require(:form),
|
id: pluck_param(:form),
|
||||||
multipart: true,
|
multipart: true,
|
||||||
class: 'form post ',
|
class: 'form post ',
|
||||||
'hx-swap': params.require(:swap),
|
'hx-swap': pluck_param(:swap),
|
||||||
'hx-target': "##{params.require(:target)}",
|
'hx-target': "##{pluck_param(:target)}",
|
||||||
'hx-validate': true,
|
'hx-validate': true,
|
||||||
data: {
|
data: {
|
||||||
controller: 'form-validation',
|
controller: 'form-validation',
|
||||||
action: 'form-validation#submit',
|
action: 'form-validation#submit',
|
||||||
'form-validation-submitting-id-value': params.permit(:submitting).values.first,
|
'form-validation-submitting-id-value': pluck_param(:submitting, optional: true),
|
||||||
'form-validation-invalid-id-value': params.permit(:invalid).values.first,
|
'form-validation-invalid-id-value': pluck_param(:invalid, optional: true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,41 +47,23 @@
|
||||||
end
|
end
|
||||||
|
|
||||||
= form_tag url, **options do
|
= form_tag url, **options do
|
||||||
- unless post.errors.empty?
|
= render 'errors', post: post
|
||||||
- title = t('.errors.title')
|
|
||||||
- help = t('.errors.help')
|
|
||||||
= render 'bootstrap/alert' do
|
|
||||||
%h4= title
|
|
||||||
%p= help
|
|
||||||
|
|
||||||
%ul
|
|
||||||
- post.errors.each do |attribute, errors|
|
|
||||||
- if errors.size > 1
|
|
||||||
%li
|
|
||||||
%strong= post_label_t attribute, post: post
|
|
||||||
%ul
|
|
||||||
- errors.each do |error|
|
|
||||||
%li= error
|
|
||||||
- else
|
|
||||||
%li
|
|
||||||
%strong= post_label_t attribute, post: post
|
|
||||||
= errors.first
|
|
||||||
|
|
||||||
-# Parámetros para HTMX
|
-# Parámetros para HTMX
|
||||||
%input{ type: 'hidden', name: 'hide', value: params.permit((post.errors.empty? ? :show : :hide)).try(:values).try(:first) }
|
%input{ type: 'hidden', name: 'hide', value: pluck_param((post.errors.empty? ? :show : :hide), optional: true) }
|
||||||
%input{ type: 'hidden', name: 'show', value: params.permit((post.errors.empty? ? :hide : :show)).try(:values).try(:first) }
|
%input{ type: 'hidden', name: 'show', value: pluck_param((post.errors.empty? ? :hide : :show), optional: true) }
|
||||||
%input{ type: 'hidden', name: 'name', value: params.require(:name) }
|
%input{ type: 'hidden', name: 'name', value: pluck_param(:name) }
|
||||||
%input{ type: 'hidden', name: 'base', value: params.require(:base) }
|
%input{ type: 'hidden', name: 'base', value: pluck_param(:base) }
|
||||||
%input{ type: 'hidden', name: 'form', value: options[:id] }
|
%input{ type: 'hidden', name: 'form', value: options[:id] }
|
||||||
%input{ type: 'hidden', name: 'dir', value: dir }
|
%input{ type: 'hidden', name: 'dir', value: dir }
|
||||||
%input{ type: 'hidden', name: 'locale', value: locale }
|
%input{ type: 'hidden', name: 'locale', value: locale }
|
||||||
%input{ type: 'hidden', name: 'attribute', value: params.require(:attribute) }
|
%input{ type: 'hidden', name: 'attribute', value: pluck_param(:attribute) }
|
||||||
%input{ type: 'hidden', name: 'target', value: params.require(:target) }
|
%input{ type: 'hidden', name: 'target', value: pluck_param(:target) }
|
||||||
%input{ type: 'hidden', name: 'swap', value: params.require(:swap) }
|
%input{ type: 'hidden', name: 'swap', value: pluck_param(:swap) }
|
||||||
- if params[:inverse].present?
|
- if params[:inverse].present?
|
||||||
%input{ type: 'hidden', name: 'inverse', value: params.require(:inverse) }
|
%input{ type: 'hidden', name: 'inverse', value: pluck_param(:inverse) }
|
||||||
- if params[:saved].present?
|
- if params[:saved].present?
|
||||||
%input{ type: 'hidden', name: 'saved', value: params.require(:saved) }
|
%input{ type: 'hidden', name: 'saved', value: pluck_param(:saved) }
|
||||||
|
|
||||||
= hidden_field_tag "#{base}[layout]", post.layout.name
|
= hidden_field_tag "#{base}[layout]", post.layout.name
|
||||||
|
|
||||||
|
@ -92,6 +74,6 @@
|
||||||
Enviamos valores vacíos o arrastrados desde el formulario anterior
|
Enviamos valores vacíos o arrastrados desde el formulario anterior
|
||||||
para los atributos ignorados
|
para los atributos ignorados
|
||||||
- except.each do |attr|
|
- except.each do |attr|
|
||||||
%input{ type: 'hidden', name: "#{base}[#{attr}]", value: params[attr].presence }
|
%input{ type: 'hidden', name: "#{base}[#{attr}]", value: pluck_param(attr, optional: true) }
|
||||||
|
|
||||||
= yield(:post_form)
|
= yield(:post_form)
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
:ruby
|
:ruby
|
||||||
local_assigns[:modal_id] ||= 'generic_modal'
|
|
||||||
|
|
||||||
image = nil
|
image = nil
|
||||||
description = nil
|
description = nil
|
||||||
|
card_id = random_id
|
||||||
|
|
||||||
if post.post.attribute?(:image) && (image = post.post.image.static_file)
|
if post.post.attribute?(:image) && (image = post.post.image.static_file)
|
||||||
description = post.post.image.value['description']
|
description = post.post.image.value['description']
|
||||||
end
|
end
|
||||||
|
|
||||||
.col.mb-3.p-1{ data: { controller: 'modal' } }
|
.col.mb-3.p-1{ id: card_id, data: { controller: 'modal' } }
|
||||||
= render('bootstrap/card', image: image, description: description, title: post.title, class: 'h-100') do
|
= render('bootstrap/card', image: image, description: description, title: post.title, class: 'h-100') do
|
||||||
- if post.post.attribute?(:description)
|
- if post.post.attribute?(:description)
|
||||||
%p.card-text= post.post.description.value
|
%p.card-text= post.post.description.value
|
||||||
|
|
||||||
|
-#
|
||||||
|
Si pasamos el ID del modal, asumimos que hay uno que ya existe y
|
||||||
|
lo llamamos. Sino, tenemos que abrir el modal genérico y cargarle
|
||||||
|
el formulario vía "HTMX".
|
||||||
|
|
||||||
|
- if local_assigns[:modal_id].present?
|
||||||
= render 'bootstrap/btn', content: t('.edit'), data: { action: 'modal#showAnother', 'modal-show-value': local_assigns[:modal_id] }, id: random_id
|
= render 'bootstrap/btn', content: t('.edit'), data: { action: 'modal#showAnother', 'modal-show-value': local_assigns[:modal_id] }, id: random_id
|
||||||
|
- else
|
||||||
|
- form_params = {}
|
||||||
|
- form_params[:layout] = post.layout
|
||||||
|
- form_params[:uuid] = post.post_id
|
||||||
|
- form_params[:modal_id] = form_params[:show] = modal_id = random_id
|
||||||
|
-# Asociar un modal con una tarjeta
|
||||||
|
- form_params[:result_id] = card_id
|
||||||
|
|
||||||
|
-# @todo Poder indicar en qué elemento queremos asociar lo descargado
|
||||||
|
= render 'bootstrap/btn', content: t('.edit'), data: { controller: 'htmx', action: 'modal#showAnother htmx#getUrlOnce', 'modal-show-value': modal_id, 'htmx-get-url-param': site_posts_modal_path(post.site, **form_params) }, id: random_id
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
|
|
||||||
@param :site [Site]
|
@param :site [Site]
|
||||||
@param :post [Post]
|
@param :post [Post]
|
||||||
= render 'posts/htmx_form', site: @site, post: @post, locale: @locale, dir: t("locales.#{@locale}.dir"), base: params.require(:base)
|
= render 'posts/htmx_form', site: @site, post: @post, locale: @locale, dir: t("locales.#{@locale}.dir"), base: pluck_param(:base)
|
||||||
|
|
55
app/views/posts/modal.haml
Normal file
55
app/views/posts/modal.haml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
-#
|
||||||
|
|
||||||
|
Genera un modal completo con el formulario del post y sus botones de
|
||||||
|
guardado.
|
||||||
|
|
||||||
|
Se comporta como "HTMX".
|
||||||
|
|
||||||
|
|
||||||
|
:ruby
|
||||||
|
post = @post
|
||||||
|
site = post.site
|
||||||
|
locale = @locale
|
||||||
|
base = random_id
|
||||||
|
dir = t("locales.#{locale}.dir")
|
||||||
|
modal_id = pluck_param(:modal_id)
|
||||||
|
result_id = pluck_param(:result_id)
|
||||||
|
form_id = random_id
|
||||||
|
except = %i[date]
|
||||||
|
options = {
|
||||||
|
id: form_id,
|
||||||
|
multipart: true,
|
||||||
|
class: 'form post'
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.new?
|
||||||
|
url = options[:'hx-post'] = site_posts_path(site, locale: locale)
|
||||||
|
options[:class] += ' new'
|
||||||
|
else
|
||||||
|
url = options[:'hx-patch'] = site_post_path(site, post.id, locale: locale)
|
||||||
|
options[:method] = :patch
|
||||||
|
options[:class] += ' edit'
|
||||||
|
end
|
||||||
|
|
||||||
|
%div{ id: modal_id, data: { controller: 'modal' }}
|
||||||
|
= render 'bootstrap/modal', id: modal_id, modal_content_attributes: { class: 'h-100' } do
|
||||||
|
- content_for :"#{modal_id}_body" do
|
||||||
|
= form_tag url, **options do
|
||||||
|
= hidden_field_tag 'base', base
|
||||||
|
= hidden_field_tag 'result_id', result_id
|
||||||
|
= hidden_field_tag 'modal_id', modal_id
|
||||||
|
= hidden_field_tag "#{base}[layout]", post.layout.name
|
||||||
|
|
||||||
|
= render 'errors', post: post
|
||||||
|
= render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale, except: except
|
||||||
|
-# @todo Volver obligatorios?
|
||||||
|
- except.each do |attr|
|
||||||
|
%input{ type: 'hidden', name: "#{base}[#{attr}]", value: pluck_param(attr, optional: true) }
|
||||||
|
|
||||||
|
- content_for :"#{modal_id}_footer" do
|
||||||
|
-# = render 'posts/validation', site: site, invalid: { id: invalid_id }, submitting: { id: submitting_id }
|
||||||
|
-# = render 'bootstrap/alert', class: 'm-0 d-none fade', id: saved_id, data: { controller: 'notification', action: 'notification:show@window->notification#show', 'notification-hide-class': 'hide', 'notification-show-class': 'show' } do
|
||||||
|
= t('.saved')
|
||||||
|
= render 'bootstrap/btn', form: form_id, content: t('.save'), type: 'submit', class: 'm-0 mt-1 mr-1'
|
||||||
|
= render 'bootstrap/btn', content: t('.close'), action: 'modal#hide', class: 'm-0 mt-1 mr-1'
|
||||||
|
= yield(:post_form)
|
|
@ -1 +1 @@
|
||||||
= render 'posts/new_has_one', post: @indexed_post, name: params.require(:name), value: @uuid
|
= render 'posts/new_has_one', post: @indexed_post, name: pluck_param(:name), value: @uuid
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
= render 'posts/new_has_one', post: @post.to_index, name: params.require(:name), value: @uuid, modal_id: params.require(:show)
|
= render 'posts/new_has_one', post: @post.to_index, name: pluck_param(:name), value: @uuid, modal_id: pluck_param(:show)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
= render 'posts/new_related_post', post: @indexed_post
|
= render 'posts/new_related_post', post: @indexed_post, modal_id: pluck_param(:modal_id, optional: true)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
= render 'sites/build', site: @site, class: params.permit(:class)[:class]
|
= render 'sites/build', site: @site, class: pluck_param(:class, optional: true)
|
||||||
|
|
|
@ -105,6 +105,7 @@ Rails.application.routes.draw do
|
||||||
get :'posts/new_related_post', to: 'posts#new_related_post'
|
get :'posts/new_related_post', to: 'posts#new_related_post'
|
||||||
get :'posts/new_has_one', to: 'posts#new_has_one'
|
get :'posts/new_has_one', to: 'posts#new_has_one'
|
||||||
get :'posts/form', to: 'posts#form'
|
get :'posts/form', to: 'posts#form'
|
||||||
|
get :'posts/modal', to: 'posts#modal'
|
||||||
|
|
||||||
resources :posts do
|
resources :posts do
|
||||||
get 'p/:page', action: :index, on: :collection
|
get 'p/:page', action: :index, on: :collection
|
||||||
|
|
BIN
public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json
(Stored with Git LFS)
BIN
public/assets/.sprockets-manifest-a1cbb907961024fc033716a7d30668dd.json
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.br
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.gz
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map.br
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map.br
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map.gz
(Stored with Git LFS)
Normal file
BIN
public/packs/js/application-bc27e0de6050d2dd9396.js.map.gz
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.br
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.br
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.gz
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.gz
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map.br
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map.br
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map.gz
(Stored with Git LFS)
BIN
public/packs/js/application-f68840b38b1cfd7c6500.js.map.gz
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/manifest.json
(Stored with Git LFS)
BIN
public/packs/manifest.json
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/manifest.json.br
(Stored with Git LFS)
BIN
public/packs/manifest.json.br
(Stored with Git LFS)
Binary file not shown.
BIN
public/packs/manifest.json.gz
(Stored with Git LFS)
BIN
public/packs/manifest.json.gz
(Stored with Git LFS)
Binary file not shown.
Loading…
Reference in a new issue