From 6ceef5465fa4a75b163f684cd5479d5e06d05d54 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 2 May 2024 16:25:20 -0300 Subject: [PATCH 01/19] chore: haml-lint --- app/views/sites/index.haml | 39 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index 840efb76..1befa2d0 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -4,7 +4,7 @@ %p.lead= t('.help') - if policy(Site).new? = link_to t('sites.new.title'), new_site_path, - class: 'btn btn-secondary' + class: 'btn btn-secondary' %section.col - if @sites.empty? @@ -15,11 +15,14 @@ %tbody - @sites.each do |site| - next unless site.jekyll? + %tr %td %h2 - if policy(site).show? - = link_to site.title, site_posts_path(site, locale: site.default_locale) + = link_to site.title, + site_posts_path(site, + locale: site.default_locale) - else = site.title %p.lead= site.description @@ -27,27 +30,27 @@ = link_to t('.visit'), site.url, class: 'btn btn-secondary' - if current_usuarie.rol_for_site(site).temporal? = render 'components/btn_base', - text: t('sites.invitations.accept'), - path: site_usuaries_accept_invitation_path(site), - title: t('help.sites.invitations.accept'), - class: 'btn-secondary' + text: t('sites.invitations.accept'), + path: site_usuaries_accept_invitation_path(site), + title: t('help.sites.invitations.accept'), + class: 'btn-secondary' = render 'components/btn_base', - text: t('sites.invitations.reject'), - path: site_usuaries_reject_invitation_path(site), - title: t('help.sites.invitations.reject'), - class: 'btn-secondary' + text: t('sites.invitations.reject'), + path: site_usuaries_reject_invitation_path(site), + title: t('help.sites.invitations.reject'), + class: 'btn-secondary' - else - if policy(site).show? = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.edit_posts'), - type: 'success', - link: site_path(site), - text: t('sites.posts') + tooltip: t('help.sites.edit_posts'), + type: 'success', + link: site_path(site), + text: t('sites.posts') = render 'sites/build', site: site = render 'sites/moderation_queue', site: site - if policy(SiteUsuarie.new(site, current_usuarie)).index? = render 'layouts/btn_with_tooltip', - tooltip: t('usuaries.index.help.self'), - text: t('usuaries.index.title'), - type: 'info', - link: site_usuaries_path(site) + tooltip: t('usuaries.index.help.self'), + text: t('usuaries.index.title'), + type: 'info', + link: site_usuaries_path(site) From 21ccedc63950cec43a929fc131f1d29b1530c1af Mon Sep 17 00:00:00 2001 From: f Date: Fri, 17 May 2024 17:00:09 -0300 Subject: [PATCH 02/19] fix: es necesario nice_partials para no tener que hacer yield --- Gemfile | 1 + Gemfile.lock | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 30794e83..500face1 100644 --- a/Gemfile +++ b/Gemfile @@ -86,6 +86,7 @@ gem 'after_commit_everywhere', '~> 1.0' gem 'aasm' gem 'que-web' gem 'nanoid' +gem 'nice_partials' # database gem 'hairtrigger' diff --git a/Gemfile.lock b/Gemfile.lock index db6b892d..a0d203d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -385,6 +385,8 @@ GEM net-protocol net-ssh (7.2.1) netaddr (2.0.6) + nice_partials (0.10.0) + actionview (>= 4.2.6) nio4r (2.7.0-x86_64-linux-musl) nokogiri (1.16.0-x86_64-linux-musl) mini_portile2 (~> 2.8.2) @@ -680,6 +682,7 @@ DEPENDENCIES mobility nanoid net-ssh + nice_partials nokogiri pg pg_search From c780466c7bb54a3ed7bce29fb83d348737c5818d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 17 May 2024 17:12:05 -0300 Subject: [PATCH 03/19] feat: a veces no se pueden agregar opciones nuevas --- app/models/metadata_new_predefined_array.rb | 4 ++ .../attribute_ro/_new_predefined_array.haml | 5 ++ .../attributes/_new_predefined_array.haml | 48 +++++++++++++++++++ config/locales/en.yml | 5 ++ config/locales/es.yml | 5 ++ 5 files changed, 67 insertions(+) create mode 100644 app/models/metadata_new_predefined_array.rb create mode 100644 app/views/posts/attribute_ro/_new_predefined_array.haml create mode 100644 app/views/posts/attributes/_new_predefined_array.haml diff --git a/app/models/metadata_new_predefined_array.rb b/app/models/metadata_new_predefined_array.rb new file mode 100644 index 00000000..8c15155f --- /dev/null +++ b/app/models/metadata_new_predefined_array.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Nueva interfaz para arrays predefinidos +class MetadataNewPredefinedArray < MetadataPredefinedArray; end diff --git a/app/views/posts/attribute_ro/_new_predefined_array.haml b/app/views/posts/attribute_ro/_new_predefined_array.haml new file mode 100644 index 00000000..88a82626 --- /dev/null +++ b/app/views/posts/attribute_ro/_new_predefined_array.haml @@ -0,0 +1,5 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td + - metadata.value.each do |v| + %span.badge.badge-primary{ dir: dir, lang: locale }= metadata.values.key v diff --git a/app/views/posts/attributes/_new_predefined_array.haml b/app/views/posts/attributes/_new_predefined_array.haml new file mode 100644 index 00000000..5203c12b --- /dev/null +++ b/app/views/posts/attributes/_new_predefined_array.haml @@ -0,0 +1,48 @@ +-# + Genera un listado de checkboxes entre los que se puede elegir para + guardar, pero no se pueden agregar nuevos. + +:ruby + id = "#{base}_#{attribute}" + name = "#{base}[#{attribute}][]" + form_id = "form-#{Nanoid.generate}" + +%div{ data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_array_value_path(site) } } + .form-group + = hidden_field_tag name, '' + = label_tag id, post_label_t(attribute, post: post) + -# Mostramos la lista de valores actuales. + + Al aceptar el modal, se vacía el listado y se completa en base a + renderizaciones con HTMX. Para poder hacer eso, tenemos que poder + acceder a todos los items dentro del modal (como array.item) y + enviar el valor al endpoint que devuelve uno por uno. Esto lo + tenemos disponible en Stimulus, pero queremos usar HTMX o técnica + similar para poder renderizar del lado del servidor. + + Para poder cancelar, mantenemos el estado original y desactivamos + o activamos los ítemes según estén incluidos en esa lista o no. + .row.row-cols-1.row-cols-md-2{ data: { target: 'array.current' } } + - metadata.values.slice(*metadata.value).each_key do |value| + = render 'posts/new_array_value', value: value + + = render 'bootstrap/btn', content: t('.edit'), action: 'modal#show' + + = render 'bootstrap/modal', id: id, modal_content_attributes: { class: 'h-100' }, hide_actions: ['array#cancel'] do + - content_for :"#{id}_header" do + .form-group.flex-grow-1.mb-0 + = label_tag id, post_label_t(attribute, post: post) + %input.form-control{ data: { target: 'array.search', action: 'input->array#search' }, type: 'search', placeholder: t('.filter') } + + - content_for :"#{id}_body" do + .form-group.mb-0{ id: "#{id}_body" } + -# Eliminamos las tildes para poder buscar independientemente de cómo se escriba + - metadata.values.each_pair do |value, key| + .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: value } } + = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: key, checked: metadata.value.include?(key), content: value + + - content_for :"#{id}_footer" do + -# Alinear los botones a la derecha + .flex-grow-1 + = render 'bootstrap/btn', content: t('.accept'), action: 'array#accept modal#hide', class: 'm-0 mr-1' + = render 'bootstrap/btn', content: t('.cancel'), action: 'array#cancel modal#hide', class: 'm-0' diff --git a/config/locales/en.yml b/config/locales/en.yml index 88b1db19..375a52b5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -724,6 +724,11 @@ en: new_array: remove: "Remove" attributes: + new_predefined_array: + edit: "Edit" + filter: "Start typing to filter..." + accept: "Accept" + cancel: "Cancel" new_array: edit: "Edit" filter: "Start typing to filter..." diff --git a/config/locales/es.yml b/config/locales/es.yml index 4945c19f..a7ae423b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -732,6 +732,11 @@ es: new_array: remove: "Eliminar" attributes: + new_predefined_array: + edit: "Editar" + filter: "Empezá a escribir para filtrar..." + accept: "Aceptar" + cancel: "Cancelar" new_array: edit: "Editar" filter: "Empezá a escribir para filtrar..." From 1e413e57f5f5a9271bbf43a647672747db59ec9e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 17 May 2024 17:47:18 -0300 Subject: [PATCH 04/19] =?UTF-8?q?feat:=20relaci=C3=B3n=20de=201:n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_new_has_many.rb | 4 ++ .../posts/attribute_ro/_new_has_many.haml | 6 +++ app/views/posts/attributes/_new_has_many.haml | 44 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 app/models/metadata_new_has_many.rb create mode 100644 app/views/posts/attribute_ro/_new_has_many.haml create mode 100644 app/views/posts/attributes/_new_has_many.haml diff --git a/app/models/metadata_new_has_many.rb b/app/models/metadata_new_has_many.rb new file mode 100644 index 00000000..3bf37d45 --- /dev/null +++ b/app/models/metadata_new_has_many.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +# Interfaz nueva para uno a muchos +class MetadataNewHasMany < MetadataHasMany; end diff --git a/app/views/posts/attribute_ro/_new_has_many.haml b/app/views/posts/attribute_ro/_new_has_many.haml new file mode 100644 index 00000000..d6b51a7a --- /dev/null +++ b/app/views/posts/attribute_ro/_new_has_many.haml @@ -0,0 +1,6 @@ +%tr{ id: attribute } + %th= post_label_t(attribute, post: post) + %td + %ul{ dir: dir, lang: locale } + - metadata.has_many.each do |p| + %li= link_to p.title.value, site_post_path(site, p.id) diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml new file mode 100644 index 00000000..9477998d --- /dev/null +++ b/app/views/posts/attributes/_new_has_many.haml @@ -0,0 +1,44 @@ +-# + Genera un listado de checkboxes entre los que se puede elegir para guardar +:ruby + id = "#{base}_#{attribute}" + name = "#{base}[#{attribute}][]" + form_id = "form-#{Nanoid.generate}" + +%div{ data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_array_value_path(site) } } + .form-group + = hidden_field_tag name, '' + = label_tag id, post_label_t(attribute, post: post) + -# Mostramos la lista de valores actuales. + + Al aceptar el modal, se vacía el listado y se completa en base a + renderizaciones con HTMX. Para poder hacer eso, tenemos que poder + acceder a todos los items dentro del modal (como array.item) y + enviar el valor al endpoint que devuelve uno por uno. Esto lo + tenemos disponible en Stimulus, pero queremos usar HTMX o técnica + similar para poder renderizar del lado del servidor. + + Para poder cancelar, mantenemos el estado original y desactivamos + o activamos los ítemes según estén incluidos en esa lista o no. + .row.row-cols-1.row-cols-md-2{ data: { target: 'array.current' } } + - metadata.values.slice(*metadata.value).each do |value| + = render 'posts/new_array_value', value: value + + = render 'bootstrap/btn', content: t('.edit'), action: 'modal#show' + + = render 'bootstrap/modal', id: id, modal_content_attributes: { class: 'h-100' }, hide_actions: ['array#cancel'] do + - content_for :"#{id}_header" do + .form-group.flex-grow-1.mb-0 + = label_tag id, post_label_t(attribute, post: post) + %input.form-control{ data: { target: 'array.search', action: 'input->array#search' }, type: 'search', placeholder: t('.filter') } + + - content_for :"#{id}_body" do + .form-group.mb-0{ id: "#{id}_body" } + - metadata.values.each_pair do |value, key| + .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: value } } + = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: key, checked: metadata.value.include?(key), content: value + + - content_for :"#{id}_footer" do + .flex-grow-1 + = render 'bootstrap/btn', content: t('.accept'), action: 'array#accept modal#hide', class: 'm-0 mr-1' + = render 'bootstrap/btn', content: t('.cancel'), action: 'array#cancel modal#hide', class: 'm-0' From f89595aff1bfbff3d0507c7383b20934ecd6b79c Mon Sep 17 00:00:00 2001 From: f Date: Fri, 17 May 2024 17:58:44 -0300 Subject: [PATCH 05/19] feat: generar la lista con links --- app/controllers/posts_controller.rb | 8 ++++++++ app/views/posts/_new_related_post.haml | 2 ++ app/views/posts/attributes/_new_has_many.haml | 8 ++++---- config/routes.rb | 1 + 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 app/views/posts/_new_related_post.haml diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index f241dfb1..aad3855d 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -32,6 +32,14 @@ class PostsController < ApplicationController render layout: false end + def new_related_post + @uuid = params.require(:value).strip + + @indexed_post = site.indexed_posts.find_by!(post_id: @uuid) + + render layout: false + end + def index authorize Post diff --git a/app/views/posts/_new_related_post.haml b/app/views/posts/_new_related_post.haml new file mode 100644 index 00000000..f3cc2a94 --- /dev/null +++ b/app/views/posts/_new_related_post.haml @@ -0,0 +1,2 @@ +.col + %p= link_to post.title, post.path diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml index 9477998d..f8ec89d4 100644 --- a/app/views/posts/attributes/_new_has_many.haml +++ b/app/views/posts/attributes/_new_has_many.haml @@ -5,7 +5,7 @@ name = "#{base}[#{attribute}][]" form_id = "form-#{Nanoid.generate}" -%div{ data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_array_value_path(site) } } +%div{ data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } .form-group = hidden_field_tag name, '' = label_tag id, post_label_t(attribute, post: post) @@ -34,9 +34,9 @@ - content_for :"#{id}_body" do .form-group.mb-0{ id: "#{id}_body" } - - metadata.values.each_pair do |value, key| - .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: value } } - = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: key, checked: metadata.value.include?(key), content: value + - metadata.values.each_pair do |value, uuid| + .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: uuid } } + = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: uuid, checked: metadata.value.include?(uuid), content: value - content_for :"#{id}_footer" do .flex-grow-1 diff --git a/config/routes.rb b/config/routes.rb index eb20edce..be50fafd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -102,6 +102,7 @@ Rails.application.routes.draw do get :'posts/new_array', to: 'posts#new_array' get :'posts/new_array_value', to: 'posts#new_array_value' + get :'posts/new_related_post', to: 'posts#new_related_post' resources :posts do get 'p/:page', action: :index, on: :collection From 03b847c946a0a9524e695fd709693a24161eac6f Mon Sep 17 00:00:00 2001 From: f Date: Fri, 17 May 2024 18:00:37 -0300 Subject: [PATCH 06/19] fix: traducciones --- config/locales/en.yml | 5 +++++ config/locales/es.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 375a52b5..6a2eb7d8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -724,6 +724,11 @@ en: new_array: remove: "Remove" attributes: + new_has_many: + edit: "Edit" + filter: "Start typing to filter..." + accept: "Accept" + cancel: "Cancel" new_predefined_array: edit: "Edit" filter: "Start typing to filter..." diff --git a/config/locales/es.yml b/config/locales/es.yml index a7ae423b..1da0edf9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -732,6 +732,11 @@ es: new_array: remove: "Eliminar" attributes: + new_has_many: + edit: "Editar" + filter: "Empezá a escribir para filtrar..." + accept: "Aceptar" + cancel: "Cancelar" new_predefined_array: edit: "Editar" filter: "Empezá a escribir para filtrar..." From 186f979157723c0514d7ae7592d98958a2afb026 Mon Sep 17 00:00:00 2001 From: fauno Date: Sat, 18 May 2024 14:22:34 +0000 Subject: [PATCH 07/19] ci: test [skip ci] From e04640c8c6d794a7b30164747b0335c41c235007 Mon Sep 17 00:00:00 2001 From: fauno Date: Sat, 18 May 2024 14:24:14 +0000 Subject: [PATCH 08/19] ci: test [skip ci] From eef6ff58c88bec560d59d7d3b9bc3f497f859f54 Mon Sep 17 00:00:00 2001 From: fauno Date: Sat, 18 May 2024 14:32:08 +0000 Subject: [PATCH 09/19] ci: test [skip ci] From 4876c61d10301e23889e4afe627e73fc53d1709f Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 12:10:37 -0300 Subject: [PATCH 10/19] =?UTF-8?q?feat:=20cargar=20el=20formulario=20del=20?= =?UTF-8?q?post=20sin=20decoraci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/posts_controller.rb | 16 ++++++++++++++++ app/views/posts/form.haml | 7 +++++++ config/routes.rb | 1 + 3 files changed, 24 insertions(+) create mode 100644 app/views/posts/form.haml diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index aad3855d..d956fd2c 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -40,6 +40,22 @@ class PostsController < ApplicationController render layout: false end + # El formulario de un Post, si pasamos el uuid, estamos editando, sino + # estamos creando. + def form + uuid = params.permit(:uuid).try(:[], :uuid) + + @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: params.require(:layout)) + end + + render layout: false + end + def index authorize Post diff --git a/app/views/posts/form.haml b/app/views/posts/form.haml new file mode 100644 index 00000000..36d15fde --- /dev/null +++ b/app/views/posts/form.haml @@ -0,0 +1,7 @@ +-# + El formulario sin ninguna decoración, para incluir dentro de otros + elementos. + + @param :site [Site] + @param :post [Post] += render 'posts/form', site: @site, post: @post diff --git a/config/routes.rb b/config/routes.rb index be50fafd..45410782 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -103,6 +103,7 @@ Rails.application.routes.draw do get :'posts/new_array', to: 'posts#new_array' get :'posts/new_array_value', to: 'posts#new_array_value' get :'posts/new_related_post', to: 'posts#new_related_post' + get :'posts/form', to: 'posts#form' resources :posts do get 'p/:page', action: :index, on: :collection From 71b87bfc0b39551e5638836143b17a8a0431d7e9 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 18:03:01 -0300 Subject: [PATCH 11/19] feat: poder abrir el modal con un evento --- .../controllers/modal_controller.js | 19 +++++++++++++++++++ app/views/posts/attributes/_new_has_many.haml | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js index 3a614b80..011c1254 100644 --- a/app/javascript/controllers/modal_controller.js +++ b/app/javascript/controllers/modal_controller.js @@ -3,8 +3,27 @@ import { Controller } from "stimulus"; export default class extends Controller { static targets = ["modal", "backdrop"]; + // TODO: Stimulus >1 + connect() { + this.showEvent = this.show.bind(this); + + window.addEventListener("modal:show", this.showEvent); + } + + // TODO: Stimulus >1 + disconnect() { + window.removeEventListener("modal:show", this.showEvent); + } + + /* + * Podemos enviar la orden de apertura como un click o como un + * CustomEvent incluyendo el id del modal como detail. + */ show(event = undefined) { event?.preventDefault(); + const modalId = event?.detail?.id; + + if (modalId && this.element.id !== modalId) return; this.modalTarget.style.display = "block"; this.backdropTarget.style.display = "block"; diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml index f8ec89d4..76cf299f 100644 --- a/app/views/posts/attributes/_new_has_many.haml +++ b/app/views/posts/attributes/_new_has_many.haml @@ -4,8 +4,9 @@ id = "#{base}_#{attribute}" name = "#{base}[#{attribute}][]" form_id = "form-#{Nanoid.generate}" + modal_id = "modal-#{Nanoid.generate}" -%div{ data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } +%div{ id: modal_id, data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } .form-group = hidden_field_tag name, '' = label_tag id, post_label_t(attribute, post: post) From 71a15ae693f25210feebcfc82cf033c1d7fc9efa Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 18:29:34 -0300 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20desde=20el=20modal,=20tener=20int?= =?UTF-8?q?erfaz=20para=20agregar=20un=20art=C3=ADculo=20nuevo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/posts/attributes/_new_has_many.haml | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml index 76cf299f..27d682c4 100644 --- a/app/views/posts/attributes/_new_has_many.haml +++ b/app/views/posts/attributes/_new_has_many.haml @@ -5,6 +5,7 @@ name = "#{base}[#{attribute}][]" form_id = "form-#{Nanoid.generate}" modal_id = "modal-#{Nanoid.generate}" + post_form_id = "post-form-#{Nanoid.generate}" %div{ id: modal_id, data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } .form-group @@ -39,7 +40,28 @@ .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: uuid } } = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: uuid, checked: metadata.value.include?(uuid), content: value + -# + Según la definición del campo, si hay un filtro, tenemos que poder + elegir qué tipo de esquema queremos o si hay uno solo, siempre + vamos a enviar ese. Si no hay ninguno, tendríamos que poder elegir + entre todos los esquemas. + - content_for :"#{id}_footer" do - .flex-grow-1 + - layout = metadata.filter[:layout] + - if layout.is_a?(String) + %input{ type: 'hidden', name: 'layout', value: layout, form: post_form_id } + = render 'bootstrap/btn', content: t('.add', layout: site.layouts[layout].humanized_name), form: post_form_id, type: 'submit', class: 'm-0 mr-1' + - else + - layouts = layout&.map { |x| site.layouts[x] } + - layouts ||= site.layouts.values + .input-group.w-auto.flex-grow-1.my-0 + %select.form-control{ form: post_form_id, name: 'layout' } + - layouts.each do |layout| + %option{ value: layout.name }= layout.humanized_name + .input-group-append + = render 'bootstrap/btn', content: t('.add', layout: ''), form: post_form_id, type: 'submit', class: 'mb-0 mr-0' = render 'bootstrap/btn', content: t('.accept'), action: 'array#accept modal#hide', class: 'm-0 mr-1' = render 'bootstrap/btn', content: t('.cancel'), action: 'array#cancel modal#hide', class: 'm-0' + +- content_for :post_form do + %form{ id: post_form_id } From 8cafe32ce7650f10624631c427cad6b46163f62e Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 18:48:49 -0300 Subject: [PATCH 13/19] =?UTF-8?q?feat:=20poder=20ocultar=20con=20un=20even?= =?UTF-8?q?to=20tambi=C3=A9n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/controllers/modal_controller.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js index 011c1254..20154f79 100644 --- a/app/javascript/controllers/modal_controller.js +++ b/app/javascript/controllers/modal_controller.js @@ -6,13 +6,16 @@ export default class extends Controller { // TODO: Stimulus >1 connect() { this.showEvent = this.show.bind(this); + this.hideEvent = this.hide.bind(this); window.addEventListener("modal:show", this.showEvent); + window.addEventListener("modal:hide", this.hideEvent); } // TODO: Stimulus >1 disconnect() { window.removeEventListener("modal:show", this.showEvent); + window.removeEventListener("modal:hide", this.hideEvent); } /* @@ -41,6 +44,9 @@ export default class extends Controller { hide(event = undefined) { event?.preventDefault(); + const modalId = event?.detail?.id; + + if (modalId && this.element.id !== modalId) return; this.backdropTarget.classList.remove("show"); this.modalTarget.classList.remove("show"); From 4f08a0e5f2e2ac47bbc181392b6569ecf8d98498 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 18:49:16 -0300 Subject: [PATCH 14/19] =?UTF-8?q?fixup!=20feat:=20desde=20el=20modal,=20te?= =?UTF-8?q?ner=20interfaz=20para=20agregar=20un=20art=C3=ADculo=20nuevo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_related_posts.rb | 12 +++++++----- config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 42d1381b..2728020e 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -26,6 +26,13 @@ class MetadataRelatedPosts < MetadataArray posts.where(uuid: value).map(&:title).map(&:value) end + # Encuentra el filtro + # + # @return [Hash] + def filter + layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys || {} + end + private # Obtiene todos los posts y opcionalmente los filtra @@ -37,11 +44,6 @@ class MetadataRelatedPosts < MetadataArray "#{post&.title&.value || post&.slug&.value} #{post&.date&.value.strftime('%F')} (#{post.layout.humanized_name})" end - # Encuentra el filtro - def filter - layout.metadata.dig(name, 'filter')&.to_h&.symbolize_keys || {} - end - def sanitize(uuid) super(uuid.map do |u| u.to_s.gsub(/[^a-f0-9\-]/i, '') diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a2eb7d8..ebc7b9c2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -729,6 +729,7 @@ en: filter: "Start typing to filter..." accept: "Accept" cancel: "Cancel" + add: "Add %{layout}" new_predefined_array: edit: "Edit" filter: "Start typing to filter..." diff --git a/config/locales/es.yml b/config/locales/es.yml index 1da0edf9..e56a09e5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -737,6 +737,7 @@ es: filter: "Empezá a escribir para filtrar..." accept: "Aceptar" cancel: "Cancelar" + add: "Agregar %{layout}" new_predefined_array: edit: "Editar" filter: "Empezá a escribir para filtrar..." From a10da6fa1c5d20ac2c582bdc9f24e7ffaa16e876 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 18 May 2024 18:50:00 -0300 Subject: [PATCH 15/19] feat: al agregar uno nuevo, cerrar el modal y abrir el formulario --- app/controllers/posts_controller.rb | 7 ++++++ app/views/posts/attributes/_new_has_many.haml | 24 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index d956fd2c..93a9983b 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -53,6 +53,13 @@ class PostsController < ApplicationController site.posts(lang: locale).build(layout: params.require(:layout)) end + triggers = {} + params.permit(:show, :hide).each_pair do |key, value| + triggers["modal:#{key}"] = { id: value } + end + + headers['HX-Trigger-After-Swap'] = triggers.to_json if triggers.present? + render layout: false end diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml index 27d682c4..f349fd6c 100644 --- a/app/views/posts/attributes/_new_has_many.haml +++ b/app/views/posts/attributes/_new_has_many.haml @@ -1,11 +1,21 @@ -# - Genera un listado de checkboxes entre los que se puede elegir para guardar + Genera un listado de checkboxes entre los que se puede elegir para + guardar. Podemos elegir entre los artículos ya cargados o agregar uno + nuevo. + + Al agregar uno nuevo, se abre un segundo modal que carga el formulario + correspondiente vía HTMX. El formulario tiene que cargarse por fuera + del formulario principal porque no se pueden anidar. + :ruby id = "#{base}_#{attribute}" name = "#{base}[#{attribute}][]" form_id = "form-#{Nanoid.generate}" modal_id = "modal-#{Nanoid.generate}" + post_id = "post-#{Nanoid.generate}" post_form_id = "post-form-#{Nanoid.generate}" + post_modal_id = "post-modal-#{Nanoid.generate}" + post_form_loaded_id = "post-loaded-#{Nanoid.generate}" %div{ id: modal_id, data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } .form-group @@ -63,5 +73,15 @@ = render 'bootstrap/btn', content: t('.accept'), action: 'array#accept modal#hide', class: 'm-0 mr-1' = render 'bootstrap/btn', content: t('.cancel'), action: 'array#cancel modal#hide', class: 'm-0' +-# + Este segundo modal es el que carga los formularios de + creación/modificación de artículos relacionados. Se envía a post_form + para que sea externo al formulario actual. - content_for :post_form do - %form{ id: post_form_id } + %form{ id: post_form_id, 'hx-get': site_posts_form_path(site), 'hx-target': "##{post_form_loaded_id}" } + %input{ type: 'hidden', name: 'show', value: post_modal_id } + %input{ type: 'hidden', name: 'hide', value: modal_id } + %div{ id: post_modal_id, data: { controller: 'modal' } } + = render 'bootstrap/modal', id: post_id, modal_content_attributes: { class: 'h-100' } do + - content_for :"#{post_id}_body" do + %div{ id: post_form_loaded_id } From 1624c62a1288735fb7028b0b8d848bf3d63b2785 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 20 May 2024 14:11:13 -0300 Subject: [PATCH 16/19] feat: poder modificar la base de los parametros del post --- app/services/post_service.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/services/post_service.rb b/app/services/post_service.rb index 84f58dad..4f5427ca 100644 --- a/app/services/post_service.rb +++ b/app/services/post_service.rb @@ -13,7 +13,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do post.draft.value = true if site.invitade? usuarie post.assign_attributes(post_params) - params.require(:post).permit(:slug).tap do |p| + params.require(base).permit(:slug).tap do |p| post.slug.value = p[:slug] if p[:slug].present? end @@ -82,7 +82,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # # { uuid => 2, uuid => 1, uuid => 0 } def reorder - reorder = params.require(:post).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i) + reorder = params.require(base).permit(reorder: {})&.dig(:reorder)&.transform_values(&:to_i) posts = site.posts(lang: locale).where(uuid: reorder.keys) files = posts.map do |post| @@ -105,6 +105,13 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do private + # La base donde buscar los parámetros + # + # @return [Symbol] + def base + @base ||= params.permit(:base).try(:[], :base).try(:to_sym) || :post + end + # Una lista de archivos a modificar # # @return [Set] @@ -126,7 +133,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do # Solo permitir cambiar estos atributos de cada articulo def post_params - @post_params ||= params.require(:post).permit(post.params).to_h + @post_params ||= params.require(base).permit(post.params).to_h end # Eliminar metadatos internos From 89f80229199d30347033e5cde6fa58322c4e4b0d Mon Sep 17 00:00:00 2001 From: f Date: Mon, 20 May 2024 14:11:39 -0300 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20recuperar=20de=20errores=20solo=20?= =?UTF-8?q?en=20producci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit en desarrollo los queremos ver! --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 117be995..f217c098 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,7 +2,7 @@ # Forma de ingreso a Sutty class ApplicationController < ActionController::Base - include ExceptionHandler + include ExceptionHandler if Rails.env.production? include Pundit::Authorization protect_from_forgery with: :null_session, prepend: true From 47655c3975560c68cd2c6336592a14292126dc20 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 20 May 2024 14:12:00 -0300 Subject: [PATCH 18/19] feat: poder detectar cuando una peticion viene de htmx --- app/controllers/application_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f217c098..0fc2440f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -117,4 +117,9 @@ class ApplicationController < ActionController::Base session[:usuarie_return_to] = request.fullpath end + + # Detecta si una petición fue hecha por HTMX + def htmx? + request.headers.key? 'HX-Request' + end end From 56003508a79964eb92ea393cd22c935b730b492c Mon Sep 17 00:00:00 2001 From: f Date: Mon, 20 May 2024 14:12:53 -0300 Subject: [PATCH 19/19] feat: poder crear un post relacionado in situ --- app/controllers/posts_controller.rb | 33 ++++++++++++--- app/views/posts/_htmx_form.haml | 41 +++++++++++++++++++ app/views/posts/attributes/_new_has_many.haml | 7 +++- app/views/posts/form.haml | 2 +- app/views/posts/new_has_many_value.haml | 2 + 5 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 app/views/posts/_htmx_form.haml create mode 100644 app/views/posts/new_has_many_value.haml diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 93a9983b..ff3ecb8e 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -44,6 +44,7 @@ class PostsController < ApplicationController # estamos creando. def form uuid = params.permit(:uuid).try(:[], :uuid) + locale @post = if uuid.present? @@ -53,12 +54,7 @@ class PostsController < ApplicationController site.posts(lang: locale).build(layout: params.require(:layout)) end - triggers = {} - params.permit(:show, :hide).each_pair do |key, value| - triggers["modal:#{key}"] = { id: value } - end - - headers['HX-Trigger-After-Swap'] = triggers.to_json if triggers.present? + swap_modals render layout: false end @@ -118,7 +114,23 @@ class PostsController < ApplicationController if @post.persisted? site.touch forget_content + end + # @todo Enviar la creación a otro endpoint para evitar tantas + # condiciones. + if htmx? + if @post.persisted? + swap_modals + + @value = @post.title.value + @uuid = @post.uuid.value + @name = params.require(:name) + + render 'posts/new_has_many_value', layout: false + else + # @todo Mostrar errores + end + elsif @post.persisted? redirect_to site_post_path(@site, @post) else render 'posts/new' @@ -216,4 +228,13 @@ class PostsController < ApplicationController def service_for_direct_upload session[:service_name] = site.name.to_sym end + + # @param triggers [Hash] Otros disparadores + def swap_modals(triggers = {}) + params.permit(:show, :hide).each_pair do |key, value| + triggers["modal:#{key}"] = { id: value } + end + + headers['HX-Trigger'] = triggers.to_json if triggers.present? + end end diff --git a/app/views/posts/_htmx_form.haml b/app/views/posts/_htmx_form.haml new file mode 100644 index 00000000..25c45aad --- /dev/null +++ b/app/views/posts/_htmx_form.haml @@ -0,0 +1,41 @@ +-# + El formulario del artículo, con HTMX activado. + + @param :site [Site] + @param :post [Post] + @param :locale [Symbol, String] + @param :dir [Symbol, String] +:ruby + options = { + multipart: true, + class: 'form post ', + 'hx-swap': params.require(:swap), + 'hx-target': "##{params.require(:target)}" + } + + 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 + += form_tag url, **options do + -# Parámetros para HTMX + %input{ type: 'hidden', name: 'hide', value: params.require(:show) } + %input{ type: 'hidden', name: 'show', value: params.require(:hide) } + %input{ type: 'hidden', name: 'name', value: params.require(:name) } + %input{ type: 'hidden', name: 'base', value: params.require(:base) } + + -# Botones de guardado + = render 'posts/submit', site: site, post: post + + = hidden_field_tag "#{base}[layout]", post.layout.name + + -# Dibuja cada atributo + = render 'posts/attributes', site: site, post: post, dir: dir, base: base, locale: locale + + -# Botones de guardado + = render 'posts/submit', site: site, post: post diff --git a/app/views/posts/attributes/_new_has_many.haml b/app/views/posts/attributes/_new_has_many.haml index f349fd6c..16d68322 100644 --- a/app/views/posts/attributes/_new_has_many.haml +++ b/app/views/posts/attributes/_new_has_many.haml @@ -16,6 +16,7 @@ post_form_id = "post-form-#{Nanoid.generate}" post_modal_id = "post-modal-#{Nanoid.generate}" post_form_loaded_id = "post-loaded-#{Nanoid.generate}" + value_list_id = "#{id}_body" %div{ id: modal_id, data: { controller: 'modal array', 'array-original-value': metadata.value.to_json, 'array-new-array-value': site_posts_new_related_post_path(site) } } .form-group @@ -45,7 +46,7 @@ %input.form-control{ data: { target: 'array.search', action: 'input->array#search' }, type: 'search', placeholder: t('.filter') } - content_for :"#{id}_body" do - .form-group.mb-0{ id: "#{id}_body" } + .form-group.mb-0{ id: value_list_id } - metadata.values.each_pair do |value, uuid| .mb-2{ data: { target: 'array.item', 'searchable-value': value.remove_diacritics.downcase, value: uuid } } = render 'bootstrap/custom_checkbox', name: name, id: "value-#{Nanoid.generate}", value: uuid, checked: metadata.value.include?(uuid), content: value @@ -81,6 +82,10 @@ %form{ id: post_form_id, 'hx-get': site_posts_form_path(site), 'hx-target': "##{post_form_loaded_id}" } %input{ type: 'hidden', name: 'show', value: post_modal_id } %input{ type: 'hidden', name: 'hide', value: modal_id } + %input{ type: 'hidden', name: 'target', value: value_list_id } + %input{ type: 'hidden', name: 'swap', value: 'beforeend' } + %input{ type: 'hidden', name: 'base', value: id } + %input{ type: 'hidden', name: 'name', value: name } %div{ id: post_modal_id, data: { controller: 'modal' } } = render 'bootstrap/modal', id: post_id, modal_content_attributes: { class: 'h-100' } do - content_for :"#{post_id}_body" do diff --git a/app/views/posts/form.haml b/app/views/posts/form.haml index 36d15fde..df4369e1 100644 --- a/app/views/posts/form.haml +++ b/app/views/posts/form.haml @@ -4,4 +4,4 @@ @param :site [Site] @param :post [Post] -= render 'posts/form', site: @site, post: @post += render 'posts/htmx_form', site: @site, post: @post, locale: @locale, dir: t("locales.#{@locale}.dir"), base: params.require(:base) diff --git a/app/views/posts/new_has_many_value.haml b/app/views/posts/new_has_many_value.haml new file mode 100644 index 00000000..b26acc89 --- /dev/null +++ b/app/views/posts/new_has_many_value.haml @@ -0,0 +1,2 @@ +.mb-2{ data: { target: 'array.item', 'searchable-value': @value.remove_diacritics.downcase, value: @uuid } } + = render 'bootstrap/custom_checkbox', name: @name, id: "value-#{Nanoid.generate}", value: @uuid, checked: true, content: @value