diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0a215c4..1f9b634 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -21,6 +21,10 @@ $form-feedback-invalid-color: $magenta; $form-feedback-icon-valid-color: $black; $component-active-bg: $magenta; +$spacers: ( + 2-plus: 0.75rem +); + @import "bootstrap"; @import "editor"; diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a4b47a1..524335a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -16,29 +16,25 @@ class PostsController < ApplicationController authorize Post @site = find_site - @category = params.dig(:category) - @layout = params.dig(:layout) - @locale = locale + @q = params[:q] + dictionary = IndexedPost.to_dictionary(locale: locale) # XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es # más simple saber si hubo cambios. - if @category || @layout || stale?(@site) - @posts = @site.posts(lang: locale) - @posts = @posts.where(categories: @category) if @category - @posts = @posts.where(layout: @layout) if @layout + if filter_params.present? || stale?(@site) + # Todos los artículos de este sitio para el idioma actual + @posts = @site.indexed_posts.where(locale: dictionary) + # 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 - @category_name = if uuid?(@category) - @site.posts(lang: locale).find(@category, uuid: true)&.title&.value - else - @category - end - # Filtrar los posts que les invitades no pueden ver @usuarie = @site.usuarie? current_usuarie - - # Orden descendiente por número y luego por fecha - @posts.sort_by!(:order, :date).reverse! end end @@ -169,4 +165,14 @@ class PostsController < ApplicationController 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).to_h.select { |_,v| v.present? } + end end diff --git a/app/models/indexed_post.rb b/app/models/indexed_post.rb index c76a5c8..7bf1ec7 100644 --- a/app/models/indexed_post.rb +++ b/app/models/indexed_post.rb @@ -28,6 +28,8 @@ class IndexedPost < ApplicationRecord # Trae los IndexedPost en el orden en que van a terminar en el sitio. default_scope lambda { order(order: :desc, created_at: :desc) } + scope :in_category, lambda { |category| where("front_matter->'categories' ? :category", category: category.to_s) } + scope :by_usuarie, lambda { |usuarie| where("front_matter->'usuaries' @> :usuarie::jsonb", usuarie: usuarie.to_s) } belongs_to :site diff --git a/app/models/post/indexable.rb b/app/models/post/indexable.rb index b887dea..bee02e5 100644 --- a/app/models/post/indexable.rb +++ b/app/models/post/indexable.rb @@ -11,7 +11,7 @@ class Post # Devuelve una versión indexable del Post # - # @return [IndexedPosts] + # @return [IndexedPost] def to_index IndexedPost.find_or_create_by(id: uuid.value).tap do |indexed_post| indexed_post.layout = layout.name @@ -41,9 +41,16 @@ class Post # # @return [Hash] def indexable_front_matter - return {} unless attribute? :categories + {}.tap do |indexable_front_matter| + indexable_front_matter = { + usuaries: usuaries.map(&:id), + draft: attribute?(:draft) ? draft.value : false + } - { categories: categories.indexable_values } + if attribute? :categories + indexable_front_matter[:categories] = categories.indexable_values + end + end end # Devuelve un documento indexable en texto plano diff --git a/app/policies/post_policy.rb b/app/policies/post_policy.rb index c22202a..69ecb18 100644 --- a/app/policies/post_policy.rb +++ b/app/policies/post_policy.rb @@ -59,9 +59,7 @@ class PostPolicy def resolve return scope if scope&.first&.site&.usuarie? usuarie - scope.select do |post| - post.usuaries.include? usuarie - end + scope.by_usuarie(usuarie.id) end end end diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index b2f3f66..9c43e43 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -3,7 +3,7 @@ @site.name, link_to(t('posts.index'), site_posts_path(@site)), - @category_name] + @category] %main.row %aside.menu.col-md-3 @@ -14,15 +14,13 @@ %table.mb-3 - @site.layouts.each do |layout| - next if layout.hidden? - - filter = params[:layout] == layout.value %tr %th= layout.humanized_name - %td.pl-3= link_to t('posts.add'), - new_site_post_path(@site, layout: layout.name), - class: 'badge badge-secondary' - %td= link_to t(filter ? 'posts.remove_filter' : 'posts.filter'), - site_posts_path(@site, layout: (filter ? nil : layout.value)), - class: 'badge badge-' + (filter ? 'primary' : 'secondary') + %td.pl-3= link_to t('posts.add'), new_site_post_path(@site, **@filter_params), class: 'badge badge-secondary' + - if @filter_params[:layout] == layout.value + %td= link_to t('posts.remove_filter'), site_posts_path(@site, **@filter_params.merge(layout: nil)), class: 'badge badge-primary' + - else + %td= link_to t('posts.filter'), site_posts_path(@site, **@filter_params.merge(layout: layout.value)), class: 'badge badge-secondary' - if policy(@site).edit? = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' @@ -48,19 +46,24 @@ %section.col = render 'layouts/flash' + .d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2 + %form + - @filter_params.each do |param, value| + - next if param == 'q' + %input{ type: 'input', name: param, value: value } + .form-group.flex-grow-0.m-0 + %input.form-control.border.border-magenta{ type: 'search', placeholder: 'Buscar', name: 'q', value: @q } + %input.sr-only{ type: 'submit' } + - if @site.locales.size > 1 + + %nav#locales + - @site.locales.each do |locale| + = link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), + class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}" - if @posts.empty? - %h2= t('posts.none') + %h2= t('posts.empty') - else = form_tag site_posts_reorder_path, method: :post do - .d-flex.justify-content-between.align-items-center - -# - TODO: Pensar una interfaz mejor para cuando haya más de tres - idiomas - - unless @site.locales.length == 1 - .locales - - @site.locales.each do |locale| - = link_to t("locales.#{locale}.name"), site_posts_path(@site, locale: locale), - class: "mr-2 mt-2 mb-2#{locale == @locale ? 'active font-weight-bold' : ''}" %table.table{ data: { controller: 'reorder' } } %caption.sr-only= t('posts.caption') %thead @@ -76,6 +79,7 @@ %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') %tbody - dir = t("locales.#{@locale}.dir") + - size = @posts.size - @posts.each_with_index do |post, i| -# TODO: Solo les usuaries cachean porque tenemos que separar @@ -84,45 +88,36 @@ TODO: Verificar qué pasa cuando se gestiona el sitio en distintos idiomas a la vez - cache_if @usuarie, post do - - checkbox_id = "checkbox-#{post.uuid.value}" - %tr{ id: post.uuid.value, data: { target: 'reorder.row' } } + - checkbox_id = "checkbox-#{post.id}" + %tr{ id: post.id, data: { target: 'reorder.row' } } %td .custom-control.custom-checkbox %input.custom-control-input{ id: checkbox_id, type: 'checkbox', autocomplete: 'off', data: { action: 'reorder#select' } } %label.custom-control-label{ for: checkbox_id } %span.sr-only= t('posts.reorder.select') -# Orden más alto es mayor prioridad - = hidden_field 'post[reorder]', post.uuid.value, - value: @posts.length - i, + = hidden_field 'post[reorder]', post.id, + value: size - i, data: { reorder: true } %td.w-100{ class: dir } - = link_to site_post_path(@site, post.id) do - %span{ lang: post.lang.value, dir: dir }= post.title.value - - if post.attributes.include? :draft - - if post.draft.value - %span.badge.badge-primary - = post_label_t(:draft, post: post) - - if post.attributes.include? :categories - - unless post.categories.value.empty? - %br - %small - - (post.categories.respond_to?(:belongs_to) ? post.categories.belongs_to : post.categories.value).each do |c| - = link_to site_posts_path(@site, category: (c.respond_to?(:uuid) ? c.uuid.value : c)) do - %span{ lang: post.lang.value, dir: dir }= (c.respond_to?(:title) ? c.title.value : c) + = link_to site_post_path(@site, post.path) do + %span{ lang: post.locale, dir: dir }= post.title + - if post.front_matter['draft'].present? + %span.badge.badge-primary + = post_label_t(:draft, post: post) + - if post.front_matter['categories'].present? + %br + %small + - post.front_matter['categories'].each do |category| + = link_to site_posts_path(@site, **@filter_params.merge(category: category)) do + %span{ lang: post.locale, dir: dir }= category %td - = post.date.value.strftime('%F') + = post.created_at.strftime('%F') %br/ - - if post.attribute? :order - = post.order.value + = post.order %td - if @usuarie || policy(post).edit? - = link_to t('posts.edit'), - edit_site_post_path(@site, post.id), - class: 'btn btn-block' + = link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block' - if @usuarie || policy(post).destroy? - = link_to t('posts.destroy'), - site_post_path(@site, post.id), - class: 'btn btn-block', - method: :delete, - data: { confirm: t('posts.confirm_destroy') } + = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') } diff --git a/config/locales/en.yml b/config/locales/en.yml index dd9f830..54b6115 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -397,6 +397,7 @@ en: en: 'English' ar: 'Arabic' posts: + empty: "There are no results for those search parameters." attribute_ro: file: download: Download file diff --git a/config/locales/es.yml b/config/locales/es.yml index 403389a..b98ae14 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -459,6 +459,7 @@ es: en: 'inglés' ar: 'árabe' posts: + empty: No hay artículos con estos parámetros de búsqueda. caption: Lista de artículos attribute_ro: file: