soporte para jekyll-ical :D
This commit is contained in:
parent
00042d3f57
commit
9db8215510
9 changed files with 195 additions and 9 deletions
|
@ -13,12 +13,18 @@ module ApplicationHelper
|
||||||
root = names.shift
|
root = names.shift
|
||||||
|
|
||||||
names.each do |n|
|
names.each do |n|
|
||||||
root += "[#{n}]"
|
root += '[' + n.to_s + ']'
|
||||||
end
|
end
|
||||||
|
|
||||||
[root, name]
|
[root, name]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def plain_field_name_for(*names)
|
||||||
|
root, name = field_name_for(*names)
|
||||||
|
|
||||||
|
root + '[' + name.to_s + ']'
|
||||||
|
end
|
||||||
|
|
||||||
def distance_of_time_in_words_if_more_than_a_minute(seconds)
|
def distance_of_time_in_words_if_more_than_a_minute(seconds)
|
||||||
if seconds > 60
|
if seconds > 60
|
||||||
distance_of_time_in_words seconds
|
distance_of_time_in_words seconds
|
||||||
|
@ -62,13 +68,15 @@ module ApplicationHelper
|
||||||
|
|
||||||
# Opciones por defecto para el campo de un formulario
|
# Opciones por defecto para el campo de un formulario
|
||||||
def field_options(attribute, metadata, **extra)
|
def field_options(attribute, metadata, **extra)
|
||||||
|
required = metadata.required || extra[:required]
|
||||||
|
|
||||||
{
|
{
|
||||||
class: "form-control #{invalid(metadata.post, attribute)} #{extra[:class]}",
|
class: "form-control #{invalid(metadata.post, attribute)} #{extra[:class]}",
|
||||||
required: metadata.required,
|
required: required,
|
||||||
autofocus: (metadata.post.attributes.first == attribute),
|
autofocus: (metadata.post.attributes.first == attribute),
|
||||||
aria: {
|
aria: {
|
||||||
describedby: id_for_help(attribute),
|
describedby: id_for_help(attribute),
|
||||||
required: metadata.required
|
required: required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,58 @@ import "prosemirror-menu/style/menu.css"
|
||||||
import "prosemirror-view/style/prosemirror.css"
|
import "prosemirror-view/style/prosemirror.css"
|
||||||
import "prosemirror-example-setup/style/style.css"
|
import "prosemirror-example-setup/style/style.css"
|
||||||
|
|
||||||
|
// Lista de equivalencias entre Date#getTimezoneOffset de JS y
|
||||||
|
// MetadataEvent
|
||||||
|
const timeZoneOffsets = {
|
||||||
|
'720': '-12:00',
|
||||||
|
'660': '-11:00',
|
||||||
|
'600': '-10:00',
|
||||||
|
'570': '-09:30',
|
||||||
|
'540': '-09:00',
|
||||||
|
'480': '-08:00',
|
||||||
|
'420': '-07:00',
|
||||||
|
'360': '-06:00',
|
||||||
|
'300': '-05:00',
|
||||||
|
'240': '-04:00',
|
||||||
|
'210': '-03:30',
|
||||||
|
'180': '-03:00',
|
||||||
|
'120': '-02:00',
|
||||||
|
'60': '-01:00',
|
||||||
|
'0': '00:00',
|
||||||
|
'-60': '+01:00',
|
||||||
|
'-120': '+02:00',
|
||||||
|
'-180': '+03:00',
|
||||||
|
'-210': '+03:30',
|
||||||
|
'-240': '+04:00',
|
||||||
|
'-270': '+04:30',
|
||||||
|
'-300': '+05:00',
|
||||||
|
'-330': '+05:30',
|
||||||
|
'-345': '+05:45',
|
||||||
|
'-360': '+06:00',
|
||||||
|
'-390': '+06:30',
|
||||||
|
'-420': '+07:00',
|
||||||
|
'-480': '+08:00',
|
||||||
|
'-525': '+08:45',
|
||||||
|
'-540': '+09:00',
|
||||||
|
'-570': '+09:30',
|
||||||
|
'-600': '+10:00',
|
||||||
|
'-630': '+10:30',
|
||||||
|
'-660': '+11:00',
|
||||||
|
'-720': '+12:00',
|
||||||
|
'-765': '+12:45',
|
||||||
|
'-780': '+13:00',
|
||||||
|
'-840': '+14:00'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Obtiene el huso horario local
|
||||||
|
const timeZoneOffset = timeZoneOffsets[(new Date).getTimezoneOffset().toString()];
|
||||||
|
|
||||||
document.addEventListener('turbolinks:load', () => {
|
document.addEventListener('turbolinks:load', () => {
|
||||||
|
|
||||||
|
// Aplicar el huso horario descubierto en los campos de evento solo
|
||||||
|
// cuando estamos creando un artículo.
|
||||||
|
document.querySelectorAll('.new .event .zone select').forEach(zone => zone.value = timeZoneOffset);
|
||||||
|
|
||||||
document.querySelectorAll('.markdown-content').forEach(mdc => {
|
document.querySelectorAll('.markdown-content').forEach(mdc => {
|
||||||
let textArea = mdc.querySelector(".content"),
|
let textArea = mdc.querySelector(".content"),
|
||||||
editor = mdc.querySelector(".editor");
|
editor = mdc.querySelector(".editor");
|
||||||
|
|
73
app/models/metadata_event.rb
Normal file
73
app/models/metadata_event.rb
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Gestiona eventos compatibles con jekyll-ical
|
||||||
|
class MetadataEvent < MetadataTemplate
|
||||||
|
# Preferimos los husos horarios en números porque los husos con
|
||||||
|
# nombres dejan afuera territorios que comparten el mismo huso que
|
||||||
|
# otras ciudades más hegemónicas.
|
||||||
|
#
|
||||||
|
# @see {https://en.wikipedia.org/wiki/List_of_UTC_time_offsets}
|
||||||
|
TIMEZONES = %w[-12:00 -11:00 -10:00 -09:30 -09:00 -08:00 -07:00 -06:00
|
||||||
|
-05:00 -04:00 -03:30 -03:00 -02:00 -01:00 00:00 +01:00 +02:00 +03:00
|
||||||
|
+03:30 +04:00 +04:30 +05:00 +05:30 +05:45 +06:00 +06:30 +07:00 +08:00
|
||||||
|
+08:45 +09:00 +09:30 +10:00 +10:30 +11:00 +12:00 +12:45 +13:00
|
||||||
|
+14:00].freeze
|
||||||
|
|
||||||
|
# El valor por defecto es un Hash con algunas llaves pero queremos que
|
||||||
|
# sea opcional.
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def default_value
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_param
|
||||||
|
{ name => {} }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Dates are required and need to be parseable
|
||||||
|
def validate
|
||||||
|
self.errors = []
|
||||||
|
times = []
|
||||||
|
|
||||||
|
%w[dtstart dtend].each do |dt|
|
||||||
|
errors << I18n.t("metadata.#{type}.zone_missing") unless TIMEZONES.include? value.dig(dt, 'zone')
|
||||||
|
errors << I18n.t("metadata.#{type}.date_missing") if value.dig(dt, 'date').blank?
|
||||||
|
|
||||||
|
begin
|
||||||
|
Date.parse value.dig(dt, 'date')
|
||||||
|
rescue ArgumentError
|
||||||
|
errors << I18n.t("metadata.#{type}.date_non_parseable")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless (time = value.dig(dt, 'time')).blank?
|
||||||
|
errors << I18n.t("metadata.#{type}.time_non_parseable") unless /[0-5][0-9]:[0-5][0-9]/ =~ time
|
||||||
|
end
|
||||||
|
|
||||||
|
times << value.dig(dt, 'date') + ' ' + value.dig(dt, 'time')
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
dtstart, dtend = times.map { |t| Time.parse t }
|
||||||
|
errors << I18n.t("metadata.#{type}.end_in_the_past") if dtstart > dtend
|
||||||
|
rescue ArgumentError
|
||||||
|
errors << I18n.t("metadata.#{type}.time_non_parseable")
|
||||||
|
end
|
||||||
|
|
||||||
|
errors.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitize(hash)
|
||||||
|
self[:value] = %w[dtstart dtend].map do |dt|
|
||||||
|
time = hash.dig(dt, 'time')
|
||||||
|
|
||||||
|
{
|
||||||
|
dt => {
|
||||||
|
'zone' => hash.dig(dt, 'zone'),
|
||||||
|
'date' => Date.parse(hash.dig(dt, 'date')).to_s,
|
||||||
|
'time' => time.blank? ? '00:00' : time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end.inject(:merge)
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,9 +22,6 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||||
end
|
end
|
||||||
|
|
||||||
# Valor actual o por defecto
|
# Valor actual o por defecto
|
||||||
#
|
|
||||||
# XXX: No estamos sanitizando la entrada, cada tipo tiene que
|
|
||||||
# auto-sanitizarse.
|
|
||||||
def value
|
def value
|
||||||
self[:value] || document.data.fetch(name.to_s, default_value)
|
self[:value] || document.data.fetch(name.to_s, default_value)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,15 +8,17 @@
|
||||||
if post.new?
|
if post.new?
|
||||||
url = site_posts_path(site, locale: @locale)
|
url = site_posts_path(site, locale: @locale)
|
||||||
method = :post
|
method = :post
|
||||||
|
extra_class = 'new'
|
||||||
else
|
else
|
||||||
url = site_post_path(site, post.id, locale: @locale)
|
url = site_post_path(site, post.id, locale: @locale)
|
||||||
method = :patch
|
method = :patch
|
||||||
|
extra_class = 'edit'
|
||||||
end
|
end
|
||||||
|
|
||||||
- dir = t("locales.#{@locale}.dir")
|
- dir = t("locales.#{@locale}.dir")
|
||||||
|
|
||||||
-# Comienza el formulario
|
-# Comienza el formulario
|
||||||
= form_tag url, method: method, class: 'form post', multipart: true do
|
= form_tag url, method: method, class: 'form post ' + extra_class, multipart: true do
|
||||||
|
|
||||||
-# Botones de guardado
|
-# Botones de guardado
|
||||||
= render 'posts/submit', site: site, post: post
|
= render 'posts/submit', site: site, post: post
|
||||||
|
|
7
app/views/posts/attribute_ro/_event.haml
Normal file
7
app/views/posts/attribute_ro/_event.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
%tr{ id: attribute }
|
||||||
|
%th= post_label_t(attribute, post: post)
|
||||||
|
%td
|
||||||
|
%dl
|
||||||
|
- %i[dtstart dtend].each do |dt|
|
||||||
|
%dt= post_label_t(attribute, dt, post: post)
|
||||||
|
%dl= Time.parse %w[date time zone].map { |x| metadata.value[dt.to_s][x] }.join(' ')
|
32
app/views/posts/attributes/_event.haml
Normal file
32
app/views/posts/attributes/_event.haml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
-#
|
||||||
|
El evento tiene dos grupos, comienzo y final del evento, cada uno con
|
||||||
|
fecha, hora y zona horaria
|
||||||
|
|
||||||
|
%fieldset.event
|
||||||
|
%legend= post_label_t(attribute, post: post)
|
||||||
|
= render 'posts/attribute_feedback',
|
||||||
|
post: post, attribute: attribute, metadata: metadata
|
||||||
|
|
||||||
|
- %i[dtstart dtend].each do |dt|
|
||||||
|
.row{ class: dt }
|
||||||
|
.col
|
||||||
|
.date.form-group
|
||||||
|
= label_tag "post_#{attribute}_#{dt}_date",
|
||||||
|
post_label_t(attribute, :date, post: post)
|
||||||
|
= date_field_tag(*field_name_for('post', attribute, dt, :date),
|
||||||
|
value: metadata.value.dig(dt.to_s, 'date'),
|
||||||
|
**field_options(attribute, metadata, required: true))
|
||||||
|
.col
|
||||||
|
.time.form-group
|
||||||
|
= label_tag "post_#{attribute}_#{dt}_time",
|
||||||
|
post_label_t(attribute, :time, post: post)
|
||||||
|
= time_field_tag(*field_name_for('post', attribute, dt, :time),
|
||||||
|
value: metadata.value.dig(dt.to_s, 'time'),
|
||||||
|
**field_options(attribute, metadata))
|
||||||
|
.col
|
||||||
|
.zone.form-group
|
||||||
|
= label_tag "post_#{attribute}_#{dt}_zone",
|
||||||
|
post_label_t(attribute, :zone, post: post)
|
||||||
|
= select_tag(plain_field_name_for('post', attribute, dt, :zone),
|
||||||
|
options_for_select(MetadataEvent::TIMEZONES, metadata.value.dig(dt.to_s, 'zone') || '00:00'),
|
||||||
|
**field_options(attribute, metadata))
|
|
@ -37,6 +37,12 @@ en:
|
||||||
cant_be_empty: 'This field cannot be empty'
|
cant_be_empty: 'This field cannot be empty'
|
||||||
image:
|
image:
|
||||||
cant_be_empty: 'This field cannot be empty'
|
cant_be_empty: 'This field cannot be empty'
|
||||||
|
event:
|
||||||
|
zone_missing: 'Timezone is incorrect'
|
||||||
|
date_missing: 'Event date is required'
|
||||||
|
date_non_parseable: 'Time is not in the correct format'
|
||||||
|
time_non_parseable: 'Date is not in the correct format'
|
||||||
|
end_in_the_past: "Event end can't happen before the start"
|
||||||
exceptions:
|
exceptions:
|
||||||
post:
|
post:
|
||||||
site_missing: 'Needs an instance of Site'
|
site_missing: 'Needs an instance of Site'
|
||||||
|
|
|
@ -39,6 +39,12 @@ es:
|
||||||
cant_be_empty: 'El campo no puede estar vacío'
|
cant_be_empty: 'El campo no puede estar vacío'
|
||||||
not_an_image: 'No es una imagen'
|
not_an_image: 'No es una imagen'
|
||||||
path_required: 'Se necesita un archivo de imagen'
|
path_required: 'Se necesita un archivo de imagen'
|
||||||
|
event:
|
||||||
|
zone_missing: 'El huso horario no es correcto'
|
||||||
|
date_missing: 'La fecha es obligatoria'
|
||||||
|
date_non_parseable: 'La fecha no está en el formato correcto'
|
||||||
|
time_non_parseable: 'La hora no está en el formato correcto'
|
||||||
|
end_in_the_past: 'El fin del evento no puede ser anterior al comienzo'
|
||||||
exceptions:
|
exceptions:
|
||||||
post:
|
post:
|
||||||
site_missing: 'Necesita una instancia de Site'
|
site_missing: 'Necesita una instancia de Site'
|
||||||
|
@ -446,9 +452,13 @@ es:
|
||||||
image:
|
image:
|
||||||
multiple: 'Puedes seleccionar varias imágenes usando la tecla <kbd>Ctrl</kbd> o <kbd>Cmd</kbd> en tu teclado.'
|
multiple: 'Puedes seleccionar varias imágenes usando la tecla <kbd>Ctrl</kbd> o <kbd>Cmd</kbd> en tu teclado.'
|
||||||
url: 'La dirección debe comenzar con http:// o https://'
|
url: 'La dirección debe comenzar con http:// o https://'
|
||||||
blank: En blanco
|
blank: Vacío
|
||||||
destroy: Borrar
|
destroy: Borrar
|
||||||
confirm_destroy: ¿Estás segurx?
|
confirm_destroy: ¿Estás segure?
|
||||||
|
form:
|
||||||
|
errors:
|
||||||
|
title: Hay errores en el formulario
|
||||||
|
help: Por favor, verifica que todos los valores sean correctos.
|
||||||
usuaries:
|
usuaries:
|
||||||
invite_as:
|
invite_as:
|
||||||
usuaries: usuaries
|
usuaries: usuaries
|
||||||
|
|
Loading…
Reference in a new issue