mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-24 18:26:22 +00:00
Merge branch 'issue-15068' into production.panel.sutty.nl
This commit is contained in:
commit
8175a45494
23 changed files with 368 additions and 7 deletions
1
Gemfile
1
Gemfile
|
@ -86,6 +86,7 @@ gem 'rubanok'
|
||||||
gem 'after_commit_everywhere', '~> 1.0'
|
gem 'after_commit_everywhere', '~> 1.0'
|
||||||
gem 'aasm'
|
gem 'aasm'
|
||||||
gem 'que-web'
|
gem 'que-web'
|
||||||
|
gem 'nanoid'
|
||||||
|
|
||||||
# database
|
# database
|
||||||
gem 'hairtrigger'
|
gem 'hairtrigger'
|
||||||
|
|
|
@ -377,6 +377,7 @@ GEM
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
mustermann (3.0.0)
|
mustermann (3.0.0)
|
||||||
ruby2_keywords (~> 0.0.1)
|
ruby2_keywords (~> 0.0.1)
|
||||||
|
nanoid (2.0.0)
|
||||||
net-imap (0.4.9)
|
net-imap (0.4.9)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
|
@ -682,6 +683,7 @@ DEPENDENCIES
|
||||||
memory_profiler
|
memory_profiler
|
||||||
mini_magick
|
mini_magick
|
||||||
mobility
|
mobility
|
||||||
|
nanoid
|
||||||
net-ssh
|
net-ssh
|
||||||
nokogiri
|
nokogiri
|
||||||
pg
|
pg
|
||||||
|
|
|
@ -22,6 +22,7 @@ $form-feedback-icon-valid-color: $black;
|
||||||
$component-active-bg: $magenta;
|
$component-active-bg: $magenta;
|
||||||
$btn-white-space: nowrap;
|
$btn-white-space: nowrap;
|
||||||
$font-weight-bolder: 700;
|
$font-weight-bolder: 700;
|
||||||
|
$zindex-modal-backdrop: 0;
|
||||||
|
|
||||||
$spacers: (
|
$spacers: (
|
||||||
2-plus: 0.75rem
|
2-plus: 0.75rem
|
||||||
|
@ -328,13 +329,13 @@ svg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-control-label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.designs {
|
.designs {
|
||||||
.design {
|
.design {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.custom-control-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,23 @@ class PostsController < ApplicationController
|
||||||
{ locale: locale }
|
{ locale: locale }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @todo Mover a tu propio scope
|
||||||
|
def new_array
|
||||||
|
@value = params.require(:value).strip
|
||||||
|
@name = params.require(:name).strip
|
||||||
|
id = params.require(:id).strip
|
||||||
|
|
||||||
|
headers['HX-Trigger-After-Swap'] = 'htmx:resetForm'
|
||||||
|
|
||||||
|
render layout: false
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_array_value
|
||||||
|
@value = params.require(:value).strip
|
||||||
|
|
||||||
|
render layout: false
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize Post
|
authorize Post
|
||||||
|
|
||||||
|
|
120
app/javascript/controllers/array_controller.js
Normal file
120
app/javascript/controllers/array_controller.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import { Controller } from "stimulus";
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["item", "search", "current"];
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// TODO: Stimulus >1
|
||||||
|
this.newArrayValueURL = new URL(window.location.origin);
|
||||||
|
this.newArrayValueURL.pathname = this.element.dataset.arrayNewArrayValue;
|
||||||
|
this.originalValue = JSON.parse(this.element.dataset.arrayOriginalValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Al eliminar el ítem, buscamos por su ID y lo eliminamos del
|
||||||
|
* documento.
|
||||||
|
*/
|
||||||
|
remove(event) {
|
||||||
|
// TODO: Stimulus >1
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.itemTargets
|
||||||
|
.find((x) => x.id === event.target.dataset.removeTargetParam)
|
||||||
|
?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Al buscar, eliminamos las tildes y mayúsculas para no depender de
|
||||||
|
* cómo se escribió.
|
||||||
|
*
|
||||||
|
* Luego buscamos eso en el valor limpio, ignorando los items que ya
|
||||||
|
* están activados.
|
||||||
|
*
|
||||||
|
* Si el término de búsqueda está vacío, volvemos a la lista original.
|
||||||
|
*/
|
||||||
|
search(event) {
|
||||||
|
const needle = this.searchTarget.value
|
||||||
|
.normalize("NFD")
|
||||||
|
.replace(/[\u0300-\u036f]/g, "")
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (needle) {
|
||||||
|
for (const itemTarget of this.itemTargets) {
|
||||||
|
itemTarget.style.display =
|
||||||
|
itemTarget.querySelector("input")?.checked ||
|
||||||
|
itemTarget.dataset.searchableValue.includes(needle)
|
||||||
|
? ""
|
||||||
|
: "none";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const itemTarget of this.itemTargets) {
|
||||||
|
itemTarget.style.display = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtiene el input de un elemento
|
||||||
|
*
|
||||||
|
* @param [HTMLElement]
|
||||||
|
* @return [HTMLElement,nil]
|
||||||
|
*/
|
||||||
|
inputFrom(target) {
|
||||||
|
if (target.tagName === "INPUT") return target;
|
||||||
|
|
||||||
|
return target.querySelector("input");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Detecta si el item es o contiene un checkbox/radio activado.
|
||||||
|
*
|
||||||
|
* @param [HTMLElement]
|
||||||
|
* @return [Bool]
|
||||||
|
*/
|
||||||
|
isChecked(itemTarget) {
|
||||||
|
return this.inputFrom(itemTarget)?.checked || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Al cancelar, se vuelve al estado original de la lista
|
||||||
|
*/
|
||||||
|
cancel(event) {
|
||||||
|
for (const itemTarget of this.itemTargets) {
|
||||||
|
const input = this.inputFrom(itemTarget);
|
||||||
|
|
||||||
|
input.checked = this.originalValue.includes(itemTarget.dataset.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Al aceptar, se envía todo el listado de valores nuevos al _backend_
|
||||||
|
* para que devuelva la representación de cada ítem en HTML. Además,
|
||||||
|
* se guarda el nuevo valor como la lista original, para la próxima
|
||||||
|
* cancelación.
|
||||||
|
*/
|
||||||
|
accept(event) {
|
||||||
|
this.currentTarget.innerHTML = "";
|
||||||
|
this.originalValue = [];
|
||||||
|
|
||||||
|
for (const itemTarget of this.itemTargets) {
|
||||||
|
if (!itemTarget.dataset.value) continue;
|
||||||
|
if (!this.isChecked(itemTarget)) continue;
|
||||||
|
|
||||||
|
this.originalValue.push(itemTarget.dataset.value);
|
||||||
|
this.newArrayValueURL.searchParams.set("value", itemTarget.dataset.value);
|
||||||
|
|
||||||
|
// TODO: Renderizarlas todas juntas
|
||||||
|
fetch(this.newArrayValueURL)
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((body) =>
|
||||||
|
this.currentTarget.insertAdjacentHTML("beforeend", body),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Stimulus >1
|
||||||
|
this.element.dataset.arrayOriginalValue = JSON.stringify(
|
||||||
|
this.originalValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
app/javascript/controllers/modal_controller.js
Normal file
40
app/javascript/controllers/modal_controller.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Controller } from "stimulus";
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["modal", "backdrop"];
|
||||||
|
|
||||||
|
show(event = undefined) {
|
||||||
|
event?.preventDefault();
|
||||||
|
|
||||||
|
this.modalTarget.style.display = "block";
|
||||||
|
this.backdropTarget.style.display = "block";
|
||||||
|
this.modalTarget.setAttribute("role", "dialog");
|
||||||
|
this.modalTarget.setAttribute("aria-modal", true);
|
||||||
|
this.modalTarget.removeAttribute("aria-hidden");
|
||||||
|
|
||||||
|
window.document.body.classList.add("modal-open");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.modalTarget.classList.add("show");
|
||||||
|
this.backdropTarget.classList.add("show");
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(event = undefined) {
|
||||||
|
event?.preventDefault();
|
||||||
|
|
||||||
|
this.backdropTarget.classList.remove("show");
|
||||||
|
this.modalTarget.classList.remove("show");
|
||||||
|
|
||||||
|
this.modalTarget.setAttribute("aria-hidden", true);
|
||||||
|
this.modalTarget.removeAttribute("role");
|
||||||
|
this.modalTarget.removeAttribute("aria-modal");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.modalTarget.style.display = "";
|
||||||
|
this.backdropTarget.style.display = "";
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
window.document.body.classList.remove("modal-open");
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,3 +5,7 @@ document.addEventListener("turbolinks:click", () => {
|
||||||
window.htmx.trigger(hx, "htmx:abort");
|
window.htmx.trigger(hx, "htmx:abort");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("htmx:resetForm", (event) => {
|
||||||
|
event.target.reset();
|
||||||
|
});
|
||||||
|
|
|
@ -42,3 +42,4 @@ Turbolinks.start()
|
||||||
ActiveStorage.start()
|
ActiveStorage.start()
|
||||||
|
|
||||||
window.htmx = require('htmx.org/dist/htmx.js')
|
window.htmx = require('htmx.org/dist/htmx.js')
|
||||||
|
window.htmx.config.selfRequestsOnly = true;
|
||||||
|
|
12
app/lib/core_extensions/string/remove_diacritics.rb
Normal file
12
app/lib/core_extensions/string/remove_diacritics.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module CoreExtensions
|
||||||
|
module String
|
||||||
|
# Elimina tildes
|
||||||
|
module RemoveDiacritics
|
||||||
|
def remove_diacritics
|
||||||
|
unicode_normalize(:nfd).gsub(/[^\x00-\x7F]/, '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
5
app/models/metadata_new_array.rb
Normal file
5
app/models/metadata_new_array.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Implementa la nueva interfaz de gestión de valores
|
||||||
|
class MetadataNewArray < MetadataArray
|
||||||
|
end
|
13
app/views/bootstrap/_btn.haml
Normal file
13
app/views/bootstrap/_btn.haml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-#
|
||||||
|
Un botón
|
||||||
|
|
||||||
|
@param :content [String] Contenido
|
||||||
|
@param :action [String] Acción de Stimulus
|
||||||
|
@param :target [String] Objetivo de Stimulus
|
||||||
|
@param [Hash] Atributos en bruto, con mayor prioridad que action y target
|
||||||
|
:ruby
|
||||||
|
attributes = local_assigns.to_h.except(:content)
|
||||||
|
attributes[:data] ||= {}
|
||||||
|
attributes[:data][:action] ||= local_assigns[:action]
|
||||||
|
attributes[:data][:target] ||= local_assigns[:target]
|
||||||
|
%button.btn.btn-secondary{ type: 'button', **attributes }= content
|
|
@ -1,6 +1,10 @@
|
||||||
- help_id = "#{id}_help"
|
:ruby
|
||||||
|
help_id = "#{id}_help"
|
||||||
|
checkbox_attributes = local_assigns.slice(:id, :type, :name, :value, :required, :checked)
|
||||||
|
checkbox_attributes[:type] ||= 'checkbox'
|
||||||
|
|
||||||
.custom-control.custom-checkbox
|
.custom-control.custom-checkbox
|
||||||
%input.custom-control-input{ id: id, type: 'checkbox', name: name, value: value, required: required }
|
%input.custom-control-input{ **checkbox_attributes }
|
||||||
%label.custom-control-label{ for: id, aria: { describedby: help_id } }= content
|
%label.custom-control-label{ for: id, aria: { describedby: help_id } }= content
|
||||||
%small.form-text.text-muted{ id: help_id }= yield
|
- if (block = yield).present?
|
||||||
|
%small.form-text.text-muted{ id: help_id }= block
|
||||||
|
|
39
app/views/bootstrap/_modal.haml
Normal file
39
app/views/bootstrap/_modal.haml
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
-#
|
||||||
|
# Modal
|
||||||
|
|
||||||
|
@see {https://getbootstrap.com/docs/4.6/components/modal/}
|
||||||
|
@see {https://github.com/bullet-train-co/nice_partials/issues/99}
|
||||||
|
@param :id [String] El ID del modal
|
||||||
|
@param :modal_content_attributes [Hash] Atributos para el contenido del modal
|
||||||
|
@param :hide_actions [Array<String>] Acciones al ocultar el modal
|
||||||
|
@yield :ID_body Contenido
|
||||||
|
@yield :ID_header Contenido del header (opcional)
|
||||||
|
@yield :ID_footer Contenido del pie (opcional)
|
||||||
|
@example
|
||||||
|
= render 'bootstrap/modal', id: 'algo' do |partial|
|
||||||
|
- content_for :algo_header do
|
||||||
|
= 'título'
|
||||||
|
- content_for :algo_body do
|
||||||
|
= 'contenido'
|
||||||
|
- content_for :algo_footer do
|
||||||
|
= 'pie'
|
||||||
|
|
||||||
|
:ruby
|
||||||
|
local_assigns[:hide_actions] ||= []
|
||||||
|
local_assigns[:hide_actions] << 'click->modal#hide'
|
||||||
|
local_assigns[:modal_content_attributes] ||= {}
|
||||||
|
|
||||||
|
.modal.fade{ tabindex: -1, aria: { hidden: 'true' }, data: { target: 'modal.modal' } }
|
||||||
|
.modal-backdrop.fade{ data: { target: 'modal.backdrop', action: local_assigns[:hide_actions].join(' ') } }
|
||||||
|
.modal-dialog.modal-dialog-scrollable.modal-dialog-centered
|
||||||
|
.modal-content{ **local_assigns[:modal_content_attributes] }
|
||||||
|
- if (header = yield(:"#{id}_header")).present?
|
||||||
|
.modal-header= header
|
||||||
|
|
||||||
|
.modal-body= yield(:"#{id}_body")
|
||||||
|
|
||||||
|
.modal-footer.flex-nowrap
|
||||||
|
- if (footer = yield(:"#{id}_footer"))
|
||||||
|
= footer
|
||||||
|
- else
|
||||||
|
%button.btn.btn-secondary.m-0{ type: 'button', data: { action: 'modal#hide' } }= t('.close')
|
|
@ -25,6 +25,7 @@
|
||||||
= render 'layouts/breadcrumb'
|
= render 'layouts/breadcrumb'
|
||||||
= render 'layouts/flash'
|
= render 'layouts/flash'
|
||||||
|
|
||||||
|
= yield(:post_form)
|
||||||
= yield
|
= yield
|
||||||
|
|
||||||
- if flash[:js]
|
- if flash[:js]
|
||||||
|
|
2
app/views/posts/_new_array_value.haml
Normal file
2
app/views/posts/_new_array_value.haml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.col
|
||||||
|
%p= value
|
8
app/views/posts/attribute_ro/_new_array.haml
Normal file
8
app/views/posts/attribute_ro/_new_array.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td
|
||||||
|
- if metadata.value.respond_to? :each
|
||||||
|
- metadata.value.each do |v|
|
||||||
|
%span.badge.badge-primary= v
|
||||||
|
- else
|
||||||
|
%span.badge.badge-primary{ lang: locale, dir: dir }= metadata.value
|
59
app/views/posts/attributes/_new_array.haml
Normal file
59
app/views/posts/attributes/_new_array.haml
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
-#
|
||||||
|
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.value.sort_by(&:remove_diacritics).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" }
|
||||||
|
-# Eliminamos las tildes para poder buscar independientemente de cómo se escriba
|
||||||
|
- metadata.values.sort_by(&:remove_diacritics).each do |value|
|
||||||
|
.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: value, checked: metadata.value.include?(value), content: value
|
||||||
|
|
||||||
|
- content_for :"#{id}_footer" do
|
||||||
|
.input-group.w-auto.flex-grow-1.my-0
|
||||||
|
%input.form-control{ form: form_id, name: 'value', type: 'text', placeholder: t('.add_new') }
|
||||||
|
.input-group-append
|
||||||
|
= render 'bootstrap/btn', content: t('.add'), form: 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'
|
||||||
|
|
||||||
|
-# Los formularios para HTMX se colocan por fuera del formulario
|
||||||
|
principal, porque HTML5 no soporta formularios anidados. Los campos
|
||||||
|
quedan unidos al formulario por su atributo `id`.
|
||||||
|
|
||||||
|
Al enviar el formulario se obtiene una nueva opción con el valor
|
||||||
|
y se la agrega al final del listado.
|
||||||
|
- content_for :post_form do
|
||||||
|
%form{ id: form_id, 'hx-get': site_posts_new_array_path(site), 'hx-target': "##{id}_body", 'hx-swap': 'beforeend' }
|
||||||
|
%input{ type: 'hidden', name: 'name', value: name }
|
||||||
|
%input{ type: 'hidden', name: 'id', value: form_id }
|
8
app/views/posts/new_array.haml
Normal file
8
app/views/posts/new_array.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
- item_id = "item-#{Nanoid.generate}"
|
||||||
|
|
||||||
|
.mb-2{ id: item_id, data: { target: 'array.item', 'searchable-value': @value.remove_diacritics.downcase, value: @value } }
|
||||||
|
.d-flex.flex-row.flex-wrap
|
||||||
|
.flex-grow-1
|
||||||
|
= render 'bootstrap/custom_checkbox', name: @name, id: "value-#{Nanoid.generate}", value: @value, checked: true, content: @value
|
||||||
|
%div
|
||||||
|
%button.btn.btn-sm.m-0{ data: { action: 'array#remove', 'remove-target-param': item_id } }= t('.remove')
|
1
app/views/posts/new_array_value.haml
Normal file
1
app/views/posts/new_array_value.haml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
= render 'posts/new_array_value', value: @value
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
String.include CoreExtensions::String::StripTags
|
String.include CoreExtensions::String::StripTags
|
||||||
|
String.include CoreExtensions::String::RemoveDiacritics
|
||||||
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
|
Jekyll::Document.include CoreExtensions::Jekyll::Document::Path
|
||||||
Jekyll::DataReader.include Jekyll::Readers::DataReaderDecorator
|
Jekyll::DataReader.include Jekyll::Readers::DataReaderDecorator
|
||||||
|
|
||||||
|
|
|
@ -717,7 +717,16 @@ en:
|
||||||
save_draft: 'Save as draft'
|
save_draft: 'Save as draft'
|
||||||
invalid_help: 'Some fields need attention! Please search for the fields marked as invalid.'
|
invalid_help: 'Some fields need attention! Please search for the fields marked as invalid.'
|
||||||
sending_help: Saving, please wait...
|
sending_help: Saving, please wait...
|
||||||
|
new_array:
|
||||||
|
remove: "Remove"
|
||||||
attributes:
|
attributes:
|
||||||
|
new_array:
|
||||||
|
edit: "Edit"
|
||||||
|
filter: "Start typing to filter..."
|
||||||
|
add_new: "Add new option"
|
||||||
|
add: "Add"
|
||||||
|
accept: "Accept"
|
||||||
|
cancel: "Cancel"
|
||||||
add: Add
|
add: Add
|
||||||
lang:
|
lang:
|
||||||
label: Language
|
label: Language
|
||||||
|
|
|
@ -725,7 +725,16 @@ es:
|
||||||
save_draft: 'Guardar como borrador'
|
save_draft: 'Guardar como borrador'
|
||||||
invalid_help: '¡Te faltan completar algunos campos! Busca los que estén marcados como inválidos'
|
invalid_help: '¡Te faltan completar algunos campos! Busca los que estén marcados como inválidos'
|
||||||
sending_help: Guardando, por favor espera...
|
sending_help: Guardando, por favor espera...
|
||||||
|
new_array:
|
||||||
|
remove: "Eliminar"
|
||||||
attributes:
|
attributes:
|
||||||
|
new_array:
|
||||||
|
edit: "Editar"
|
||||||
|
filter: "Empezá a escribir para filtrar..."
|
||||||
|
add_new: "Agregar nueva opción"
|
||||||
|
add: "Agregar"
|
||||||
|
accept: "Aceptar"
|
||||||
|
cancel: "Cancelar"
|
||||||
add: Agregar
|
add: Agregar
|
||||||
lang:
|
lang:
|
||||||
label: Idioma
|
label: Idioma
|
||||||
|
|
|
@ -99,6 +99,10 @@ Rails.application.routes.draw do
|
||||||
nested do
|
nested do
|
||||||
scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do
|
scope '/(:locale)', constraint: /[a-z]{2}(-[A-Z]{2})?/ do
|
||||||
post :'posts/reorder', to: 'posts#reorder'
|
post :'posts/reorder', to: 'posts#reorder'
|
||||||
|
|
||||||
|
get :'posts/new_array', to: 'posts#new_array'
|
||||||
|
get :'posts/new_array_value', to: 'posts#new_array_value'
|
||||||
|
|
||||||
resources :posts do
|
resources :posts do
|
||||||
get 'p/:page', action: :index, on: :collection
|
get 'p/:page', action: :index, on: :collection
|
||||||
get :preview, to: 'posts#preview'
|
get :preview, to: 'posts#preview'
|
||||||
|
|
Loading…
Reference in a new issue