soporte para jekyll-ical :D

This commit is contained in:
f 2020-06-27 21:42:15 -03:00
parent 00042d3f57
commit 9db8215510
9 changed files with 195 additions and 9 deletions

View file

@ -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

View file

@ -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");

View 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

View file

@ -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

View file

@ -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

View 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(' ')

View 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))

View file

@ -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'

View file

@ -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