mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-24 08:46:22 +00:00
Merge branch 'issue-15068' into production.panel.sutty.nl
This commit is contained in:
commit
277b03eab1
21 changed files with 360 additions and 27 deletions
1
Gemfile
1
Gemfile
|
@ -87,6 +87,7 @@ gem 'after_commit_everywhere', '~> 1.0'
|
|||
gem 'aasm'
|
||||
gem 'que-web'
|
||||
gem 'nanoid'
|
||||
gem 'nice_partials'
|
||||
|
||||
# database
|
||||
gem 'hairtrigger'
|
||||
|
|
|
@ -389,6 +389,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.4-x86_64-linux-musl)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
|
@ -685,6 +687,7 @@ DEPENDENCIES
|
|||
mobility
|
||||
nanoid
|
||||
net-ssh
|
||||
nice_partials
|
||||
nokogiri
|
||||
pg
|
||||
pg_search
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -32,6 +32,33 @@ 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
|
||||
|
||||
# El formulario de un Post, si pasamos el uuid, estamos editando, sino
|
||||
# estamos creando.
|
||||
def form
|
||||
uuid = params.permit(:uuid).try(:[], :uuid)
|
||||
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: params.require(:layout))
|
||||
end
|
||||
|
||||
swap_modals
|
||||
|
||||
render layout: false
|
||||
end
|
||||
|
||||
def index
|
||||
authorize Post
|
||||
|
||||
|
@ -87,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'
|
||||
|
@ -185,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
|
||||
|
|
|
@ -3,8 +3,30 @@ import { Controller } from "stimulus";
|
|||
export default class extends Controller {
|
||||
static targets = ["modal", "backdrop"];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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";
|
||||
|
@ -22,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");
|
||||
|
|
4
app/models/metadata_new_has_many.rb
Normal file
4
app/models/metadata_new_has_many.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Interfaz nueva para uno a muchos
|
||||
class MetadataNewHasMany < MetadataHasMany; end
|
4
app/models/metadata_new_predefined_array.rb
Normal file
4
app/models/metadata_new_predefined_array.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Nueva interfaz para arrays predefinidos
|
||||
class MetadataNewPredefinedArray < MetadataPredefinedArray; end
|
|
@ -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, '')
|
||||
|
|
|
@ -15,7 +15,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
|
||||
|
||||
|
@ -87,7 +87,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|
|
||||
|
@ -110,6 +110,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]
|
||||
|
@ -131,7 +138,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
|
||||
|
|
41
app/views/posts/_htmx_form.haml
Normal file
41
app/views/posts/_htmx_form.haml
Normal file
|
@ -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
|
2
app/views/posts/_new_related_post.haml
Normal file
2
app/views/posts/_new_related_post.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
.col
|
||||
%p= link_to post.title, post.path
|
6
app/views/posts/attribute_ro/_new_has_many.haml
Normal file
6
app/views/posts/attribute_ro/_new_has_many.haml
Normal file
|
@ -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)
|
5
app/views/posts/attribute_ro/_new_predefined_array.haml
Normal file
5
app/views/posts/attribute_ro/_new_predefined_array.haml
Normal file
|
@ -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
|
92
app/views/posts/attributes/_new_has_many.haml
Normal file
92
app/views/posts/attributes/_new_has_many.haml
Normal file
|
@ -0,0 +1,92 @@
|
|||
-#
|
||||
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}"
|
||||
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
|
||||
= 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: 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
|
||||
|
||||
-#
|
||||
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
|
||||
- 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'
|
||||
|
||||
-#
|
||||
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, '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
|
||||
%div{ id: post_form_loaded_id }
|
48
app/views/posts/attributes/_new_predefined_array.haml
Normal file
48
app/views/posts/attributes/_new_predefined_array.haml
Normal file
|
@ -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'
|
7
app/views/posts/form.haml
Normal file
7
app/views/posts/form.haml
Normal file
|
@ -0,0 +1,7 @@
|
|||
-#
|
||||
El formulario sin ninguna decoración, para incluir dentro de otros
|
||||
elementos.
|
||||
|
||||
@param :site [Site]
|
||||
@param :post [Post]
|
||||
= render 'posts/htmx_form', site: @site, post: @post, locale: @locale, dir: t("locales.#{@locale}.dir"), base: params.require(:base)
|
2
app/views/posts/new_has_many_value.haml
Normal file
2
app/views/posts/new_has_many_value.haml
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -720,6 +720,17 @@ en:
|
|||
new_array:
|
||||
remove: "Remove"
|
||||
attributes:
|
||||
new_has_many:
|
||||
edit: "Edit"
|
||||
filter: "Start typing to filter..."
|
||||
accept: "Accept"
|
||||
cancel: "Cancel"
|
||||
add: "Add %{layout}"
|
||||
new_predefined_array:
|
||||
edit: "Edit"
|
||||
filter: "Start typing to filter..."
|
||||
accept: "Accept"
|
||||
cancel: "Cancel"
|
||||
new_array:
|
||||
edit: "Edit"
|
||||
filter: "Start typing to filter..."
|
||||
|
|
|
@ -728,6 +728,17 @@ es:
|
|||
new_array:
|
||||
remove: "Eliminar"
|
||||
attributes:
|
||||
new_has_many:
|
||||
edit: "Editar"
|
||||
filter: "Empezá a escribir para filtrar..."
|
||||
accept: "Aceptar"
|
||||
cancel: "Cancelar"
|
||||
add: "Agregar %{layout}"
|
||||
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..."
|
||||
|
|
|
@ -102,6 +102,8 @@ 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
|
||||
|
|
Loading…
Reference in a new issue