# frozen_string_literal: true # Controlador para artículos class PostsController < ApplicationController include StrongParamsHelper before_action :authenticate_usuarie! before_action :service_for_direct_upload, only: %i[new edit] # TODO: Traer los comunes desde ApplicationController breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path breadcrumb 'sites.index', :sites_path, match: :exact breadcrumb -> { site.title }, -> { site_posts_path(site, locale: locale) }, match: :exact # Las URLs siempre llevan el idioma actual o el de le usuarie def default_url_options { locale: locale } end # @todo Mover a tu propio scope def new_array @value = pluck_param(:value) @name = pluck_param(:name) id = pluck_param(:id) headers['HX-Trigger-After-Swap'] = 'htmx:resetForm' render layout: false end def new_array_value @value = pluck_param(:value) render layout: false end def new_related_post @uuid = pluck_param(:value) @indexed_post = site.indexed_posts.find_by!(post_id: @uuid) render layout: false end def new_has_one @uuid = pluck_param(:value) @indexed_post = site.indexed_posts.find_by!(post_id: @uuid) render layout: false end # El formulario de un Post, si pasamos el UUID, estamos editando, sino # estamos creando. def form uuid = pluck_param(:uuid, optional: true) locale @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 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 swap_modals render layout: false end def index authorize Post # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. return unless stale?([current_usuarie, site, filter_params]) # Todos los artículos de este sitio para el idioma actual @posts = site.indexed_posts.where(locale: locale) @posts = @posts.page(filter_params.delete(:page)) if site.pagination # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría @posts = @posts.in_category(filter_params[:category]) if filter_params[:category] # Aplicar los parámetros de búsqueda @posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present? # A los que este usuarie tiene acceso @posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve # Filtrar los posts que les invitades no pueden ver @usuarie = site.usuarie? current_usuarie @site_stat = SiteStat.new(site) end def show authorize post breadcrumb post.title.value, '' fresh_when post end # Genera una previsualización del artículo. def preview authorize post render html: post.render end def new authorize Post @post = site.posts(lang: locale).build(layout: pluck_param(:layout)) breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), '' end def create authorize Post service = PostService.new(site: site, usuarie: current_usuarie, params: params) @post = service.create_or_update 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? triggers = { 'notification:show' => { 'id' => pluck_param(:saved, optional: true) } } swap_modals(triggers) @value = post.title.value @uuid = post.uuid.value @name = pluck_param(:name) render render_path_from_attribute, layout: false else headers['HX-Retarget'] = "##{pluck_param(:form)}" headers['HX-Reswap'] = 'outerHTML' render 'posts/form', layout: false, post: post, site: site, **params.permit(:form, :base, :dir, :locale) end elsif post.persisted? redirect_to site_post_path(site, post) else render 'posts/new' end end def edit authorize post breadcrumb post.title.value, site_post_path(site, post, locale: locale), match: :exact breadcrumb 'posts.edit', '' 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 authorize post service = PostService.new(site: site, post: post, usuarie: current_usuarie, params: params) if service.update.persisted? site.touch forget_content end if htmx? if post.persisted? triggers = { 'notification:show' => pluck_param(:saved, optional: true) } swap_modals(triggers) @value = post.title.value @uuid = post.uuid.value 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 end else headers['HX-Retarget'] = "##{params.require(:form)}" headers['HX-Reswap'] = 'outerHTML' render 'posts/form', layout: false, post: post, site: site, **params.permit(:form, :base, :dir, :locale) end elsif post.persisted? redirect_to site_post_path(site, post) else render 'posts/edit' end end # Eliminar artículos def destroy authorize post service = PostService.new(site: site, post: post, usuarie: current_usuarie, params: params) # TODO: Notificar si se pudo o no service.destroy site.touch redirect_to site_posts_path(site, locale: post.lang.value) end # Reordenar los artículos def reorder authorize site service = PostService.new(site: site, usuarie: current_usuarie, params: params) service.reorder site.touch redirect_to site_posts_path(site, locale: site.default_locale) end # Devuelve el idioma solicitado a través de un parámetro, validando # que el sitio soporte ese idioma, de lo contrario devuelve el idioma # actual. # # TODO: Debería devolver un error o mostrar una página donde se # solicite a le usuarie crear el nuevo idioma y que esto lo agregue al # _config.yml del sitio en lugar de mezclar idiomas. def locale @locale ||= site&.locales&.find(-> { site&.default_locale }) do |l| l.to_s == params[:locale] end end # Instruye al editor a olvidarse el contenido del artículo. Usar # cuando hayamos guardado la información correctamente. def forget_content flash[:js] = { target: 'editor', action: 'forget-content', keys: (params[:storage_keys] || []).to_json } end private # Los parámetros de filtros que vamos a mantener en todas las URLs, # solo los que no estén vacíos. # # @return [Hash] def filter_params @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v| v.present? end.transform_keys(&:to_sym) end def post @post ||= site.posts(lang: locale).find(params[:post_id] || params[:id]) end # Recuerda el nombre del servicio de subida de archivos 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 } if value.present? end headers['HX-Trigger'] = triggers.to_json if triggers.present? end # @return [String] def render_path_from_attribute case pluck_param(:attribute) when 'new_has_many' then 'posts/new_has_many_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_one' then 'posts/new_has_one_value' else 'nothing' end end end