Mapa conferencia y algunos puntos de interés que no te puedes perder
+
+
+
+
+
Cronograma del evento ⏲
+
+
+
+
Aquí tienes un cronograma de las etapas por las que irá pasando la PyConEs. Actualizaremos las fechas exactas en breve.
+
Para no perderte un detalle te recomendamos revisar la web, subscribirte a la newsletter y seguirnos por twitter
+
+
+
+
+
+
+
+
+ Inicio
+
+
+
+
+ Enero
+
Os presentamos nuestro fabuloso evento 🎊
+
+
+
+
+
+
Marzo
+
Abrimos envío de propuesta a ponencias ✌
+
+
+
+
+
+
+
Abril
+
Apertura venta de entradas 🛒
+
+
+
+
+
+
Junio
+
Revisamos las ponencias enviadas 🔍
+
+
+
+
+
+
Julio
+
Notificamos las ponencias aceptadas 🎸
+
+
+
+
+
+
+
+
+
30 Septiembre
+
Recepción y primera jornada del evento
+
+
+
+
+
+
+
+
+
1 Octubre
+
Segunda jornada del evento
+
+
+
+
+
+
+
+
2 Octubre
+
Tercera jornada del evento y clausura
+
+
+
+ Fin
+
+
+
+
+
+
+
Localización 🗺
+
Se celebrará en el nuevo campus universitario ubicado en el Parque Tecnológico de la Salud,
+Dirección: Av. de la Ilustración, 80, 18016 Granada
+
+
+
+
+
+
+
+
+
+
+
+
Mapa del espacio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Patrocinios 🏢
+
+ Gracias a las empresas que colaboran con la PyConES
+ podemos ofrecer el mejor evento y experiencia posible.
+
+
+ Somos una conferencia con un bajo coste de entrada
+ capaz de ofrecer una experiencia de 3 días incluyendo
+ regalos, almuerzos, comidas y meriendas.
+
+
+ Además contamos con servicio de guardería, becas y
+ traducción en directo a lenguaje de signos
+ para que nadie se quede fuera.
+
+
+ Con la ayuda de estas empresas conseguimos
+ hacer un evento diverso e inclusivo enfocado
+ en cuidar la comunidad de Python.
+
Aquí tienes un cronograma de las etapas por las que irá pasando la PyConEs. Actualizaremos las fechas exactas en breve.
+
Para no perderte un detalle te recomendamos revisar la web, subscribirte a la newsletter y seguirnos por twitter
+
+
+
+
+
+
+
+
+ Inicio
+
+
+
+
+ Enero
+
Os presentamos nuestro fabuloso evento 🎊
+
+
+
+
+
+
Marzo
+
Abrimos envío de propuesta a ponencias ✌
+
+
+
+
+
+
+
Abril
+
Apertura venta de entradas 🛒
+
+
+
+
+
+
Junio
+
Revisamos las ponencias enviadas 🔍
+
+
+
+
+
+
Julio
+
Notificamos las ponencias aceptadas 🎸
+
+
+
+
+
+
+
+
+
30 Septiembre
+
Recepción y primera jornada del evento
+
+
+
+
+
+
+
+
+
1 Octubre
+
Segunda jornada del evento
+
+
+
+
+
+
+
+
2 Octubre
+
Tercera jornada del evento y clausura
+
+
+
+ Fin
+
+
+
+
+
+
+
Localización 🗺
+
Se celebrará en el nuevo campus universitario ubicado en el Parque Tecnológico de la Salud,
+Dirección: Av. de la Ilustración, 80, 18016 Granada
+
+
+
+
+
+
+
+
+
+
+
+
Mapa del espacio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Patrocinios 🏢
+
+ Gracias a las empresas que colaboran con la PyConES
+ podemos ofrecer el mejor evento y experiencia posible.
+
+
+ Somos una conferencia con un bajo coste de entrada
+ capaz de ofrecer una experiencia de 3 días incluyendo
+ regalos, almuerzos, comidas y meriendas.
+
+
+ Además contamos con servicio de guardería, becas y
+ traducción en directo a lenguaje de signos
+ para que nadie se quede fuera.
+
+
+ Con la ayuda de estas empresas conseguimos
+ hacer un evento diverso e inclusivo enfocado
+ en cuidar la comunidad de Python.
+
Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.
+
Las becas se destinarán a sufragar los gastos de alojamiento, el transporte y/o la entrada del evento.
+
Estas becas están dirigidas a:
+
+
Estudiantes universitarios, formación profesional o BootCamp
+
Personas en situación de desempleo.
+
Familias monoparentales.
+
Personas con contrato de prácticas, jornada reducida o baja laboral.
+
+
Documentación a aportar:
+
+
Estudiantes: presentar el resguardo de la matrícula del curso vigente.
+
Desempleados: certificado de desempleo.
+
Familias monoparentales: documento que certifique la situación. Recuerde censurar la información comprometida del documento.
+
Contratos en prácticas, jornada reducida o baja laboral: documento que certifique la situación. Recuerde censurar la información comprometida del documento.
+
+
Para solicitar la beca debe rellenar este formulario y tras estudiar la solicitud nos pondremos en contacto vía email con la respuesta.
+El plazo de postulación es hasta el 15 de Julio.
+
Por favor, si tuviera cualquier duda o tuviera una situación compleja y no se viera reflejada en los supuestos contemplados comuníquenoslo a becas@2022.es.pycon.org
+y estudiaremos la forma de ayudarle.
+
Toda la información la ponemos en los canales oficiales, no olvide suscribirse. Muchas gracias por hacer todo lo posible por acudir a la PyConES 2022.
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/blog.html b/blog.html
new file mode 100644
index 000000000..e78e118a9
--- /dev/null
+++ b/blog.html
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+
+ PyConES 2022 GRX - Blog Index
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten. Plazo postulación hasta el 15 de Julio.
Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten. Plazo postulación hasta el 15 de Julio.
Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia.
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elaboracion-programa.html b/elaboracion-programa.html
new file mode 100644
index 000000000..2407a30eb
--- /dev/null
+++ b/elaboracion-programa.html
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+ Elaboración del programa - PyConES 2022 GRX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Elaboración del programa
+
+
+
Proceso de revisión y elaboración del programa
+
En toda conferencia donde se admiten propuestas hay al menos dos fases: 1) revisión y 2) elaboración del programa.
+
La revisión la lleva a cabo un "comité de expertos/as" que en nuestro caso estará formado por todas las personas que se han ofrecido desinteresadamente entre las comunidades de Python en España. Esta revisión es a ciegas, es decir, el equipo de revisión no sabrá quiénes son las personas que proponen esa charla o taller.
+
La elaboración del programa generalmente la hace una persona, pero en nuestro caso lo haremos un grupo reducido de personas de la organización ("comité de programa"). Este grupo seleccionará las charlas y talleres usando las puntuaciones y comentarios de la primera fase e intentará que el programa sea lo más variado (temáticas) y diverso posible.
+
Criterios de revisión
+
Queremos seleccionar las mejores propuestas y que aprendamos un montón en la PyConES. Estos son algunos puntos a considerar durante la revisión:
+
+
Relevancia: ¿cómo de relevante es la propuesta para la PyConES y para la temática elegida?
+
Originalidad: ¿es una propuesta original y diferente que pueda interesar a la audiencia?
+
Claridad: ¿se entiende bien el título y el resumen de la propuesta?
+
¿Crees que la propuesta se puede contar en el tiempo establecido (charla = 30 minutos, taller corto = hasta 2h, taller largo = hasta 4h)?
+
+
+
+
\ No newline at end of file
diff --git a/extra/manifest.json b/extra/manifest.json
new file mode 100644
index 000000000..9287dcd8e
--- /dev/null
+++ b/extra/manifest.json
@@ -0,0 +1,31 @@
+{
+ "theme_color": "#3d0f17",
+ "background_color": "#3d0f17",
+ "display": "fullscreen",
+ "scope": "/",
+ "start_url": "/",
+ "name": "PyConES GRX",
+ "short_name": "PyConES GRX",
+ "icons": [
+ {
+ "src": "/theme/images/icons/icon-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/theme/images/icons/icon-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ },
+ {
+ "src": "/theme/images/icons/icon-384x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ },
+ {
+ "src": "/theme/images/icons/icon-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/feeds/all-es.atom.xml b/feeds/all-es.atom.xml
new file mode 100644
index 000000000..5ee605f5b
--- /dev/null
+++ b/feeds/all-es.atom.xml
@@ -0,0 +1,187 @@
+
+PyConES 2022 GRXhttps://2022.es.pycon.org/2022-10-04T18:30:00+02:00Elaboración del programa2022-10-04T18:30:00+02:002022-10-04T18:30:00+02:00Python Españatag:2022.es.pycon.org,2022-10-04:/elaboracion-programa.html<p>Proceso de revisión y elaboración del programa</p><h1>Proceso de revisión y elaboración del programa</h1>
+<p>En toda conferencia donde se admiten propuestas hay al menos dos fases: 1) revisión y 2) elaboración del programa.</p>
+<p>La revisión la lleva a cabo un "comité de expertos/as" que en nuestro caso estará formado por todas las personas que se han ofrecido desinteresadamente entre las comunidades de Python en España. Esta revisión es a ciegas, es decir, el equipo de revisión no sabrá quiénes son las personas que proponen esa charla o taller.</p>
+<p>La elaboración del programa generalmente la hace una persona, pero en nuestro caso lo haremos un grupo reducido de personas de la organización ("comité de programa"). Este grupo seleccionará las charlas y talleres usando las puntuaciones y comentarios de la primera fase e intentará que el programa sea lo más variado (temáticas) y diverso posible.</p>
+<h1>Criterios de revisión</h1>
+<p>Queremos seleccionar las mejores propuestas y que aprendamos un montón en la PyConES. Estos son algunos puntos a considerar durante la revisión:</p>
+<ul>
+<li><em>Relevancia</em>: ¿cómo de relevante es la propuesta para la PyConES y para la temática elegida?</li>
+<li><em>Originalidad</em>: ¿es una propuesta original y diferente que pueda interesar a la audiencia?</li>
+<li><em>Claridad</em>: ¿se entiende bien el título y el resumen de la propuesta?</li>
+<li>¿Crees que la propuesta se puede contar en el <em>tiempo</em> establecido (charla = 30 minutos, taller corto = hasta 2h, taller largo = hasta 4h)?</li>
+</ul>
+<h1>Comité de programa</h1>
+<ul>
+<li><a href="https://www.linkedin.com/in/jimena-eb/">Jimena Bermúdez</a></li>
+<li><a href="https://www.linkedin.com/in/maria-moreno-de-castro/">Dra. María Moreno de Castro</a></li>
+<li><a href="https://www.linkedin.com/in/cmaureir/">Dr. Cristián Maureira-Fredes</a></li>
+<li><a href="https://www.linkedin.com/in/draxus/">Dr. Manuel Martín Salvador</a></li>
+</ul>
+<h1>Equipo de revisión</h1>
+<ul>
+<li>Alberto Fernandez</li>
+<li>Alejandro Almira</li>
+<li>Alicia Pérez Jiménez</li>
+<li>Álvaro Torres Carrasco</li>
+<li>Andrés</li>
+<li>Angela Dini</li>
+<li>Antonio Ramírez</li>
+<li>Carlos Perales</li>
+<li>Carmel</li>
+<li>Christian</li>
+<li>Cristián Maureira-Fredes</li>
+<li>Daniel Domene</li>
+<li>David Arcos</li>
+<li>Diomedes Díaz</li>
+<li>Fernando Salamero</li>
+<li>Héctor Canto</li>
+<li>Ignasi</li>
+<li>Jaime Arrazola</li>
+<li>Javier Cordero</li>
+<li>Jimena</li>
+<li>Jose Alberto Torres Agüera</li>
+<li>Jose Manuel Ortega</li>
+<li>José Miguel López</li>
+<li>Jose Nicolielly Leal</li>
+<li>Juan Antonio Fernández</li>
+<li>Juan Cruz-Benito</li>
+<li>Juan J. Merelo</li>
+<li>Juan José</li>
+<li>Juan Luis Cano Rodríguez</li>
+<li>Kevin Eliezer</li>
+<li>Loris Mularoni</li>
+<li>Manuel Calzado Vidal</li>
+<li>Manuel Martín Salvador</li>
+<li>Maria Moreno de Castro</li>
+<li>Marta Gomez Macias</li>
+<li>Martín Torre Castro</li>
+<li>Martina</li>
+<li>Mateo</li>
+<li>Miguel Hernández</li>
+<li>Miguel Olivares</li>
+<li>Pablo Benavides</li>
+<li>Pablo García</li>
+<li>Paloma de las Cuevas</li>
+<li>Raúl</li>
+<li>Ruben Berenguel</li>
+<li>Ruben Espinosa</li>
+<li>serfer2</li>
+<li>Toni Grabulosa</li>
+<li>Víctor</li>
+<li>Victor Suarez</li>
+<li>Xavi Torelló</li>
+</ul>Taller Django Girls2022-08-23T22:00:00+02:002022-08-23T22:00:00+02:00Python España Orgtag:2022.es.pycon.org,2022-08-23:/taller-djangogirls.html<p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales …</p><p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales utilizan diariamente. ¡Y de una manera muy
+sencilla!</p>
+<p>Será el viernes 30 de Septiembre en la sala Paraninfo del nuevo campos del
+Parque Tecnológico de la Salud de la universidad de Granada, durante el primer
+día de la conferencia nacional de Python más importante de España: PyConES
+2022.</p>
+<p>El taller es gratuito, pero no esperes mucho para apuntarte, las plazas son
+limitadas.</p>
+<p>Si tienes ganas de aprender a programar y tienes un portátil, puedes apuntarte
+a nuestro evento. No necesitas tener ningún conocimiento previo, pero sí mucha
+motivación de adquirirlo rápidamente el día del taller.</p>
+<p>Como asistente al taller, tú podrás:</p>
+<ul>
+<li>Participar en un taller intensivo de Django de un día de duración en el que
+ crearás tu propia página web.</li>
+<li>Disfrutar de un ambiente distendido, en el que aprenderemos casi jugando, con
+ mucho buen rollo, y algún que otro detalle.</li>
+</ul>
+<p>Tenemos un cupo máximo para 30 personas, así que tratá de inscribirte lo antes posible! 😃</p>
+<p><a href="https://djangogirls.org/en/granada/">Inscripciones en el Sitio web de Django Girls</a></p>Becas y ayudas para Pythonistas ❇2022-04-29T14:20:00+02:002022-04-29T14:20:00+02:00Python España Orgtag:2022.es.pycon.org,2022-04-29:/becas.html<p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán …</p><p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán a sufragar los gastos de alojamiento, el transporte y/o la entrada del evento.</p>
+<p>Estas becas están dirigidas a:</p>
+<ul>
+<li>Estudiantes universitarios, formación profesional o BootCamp</li>
+<li>Personas en situación de desempleo.</li>
+<li>Familias monoparentales.</li>
+<li>Personas con contrato de prácticas, jornada reducida o baja laboral.</li>
+</ul>
+<p>Documentación a aportar:</p>
+<ul>
+<li>Estudiantes: presentar el resguardo de la matrícula del curso vigente.</li>
+<li>Desempleados: certificado de desempleo.</li>
+<li>Familias monoparentales: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+<li>Contratos en prácticas, jornada reducida o baja laboral: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+</ul>
+<p>Para solicitar la beca debe rellenar este <a href="https://forms.gle/4sBjJXcGvXnk6PEfA">formulario</a> y tras estudiar la solicitud nos pondremos en contacto vía email con la respuesta.
+<strong>El plazo de postulación es hasta el 15 de Julio.</strong></p>
+<p>Por favor, si tuviera cualquier duda o tuviera una situación compleja y no se viera reflejada en los supuestos contemplados comuníquenoslo a <a href="mailto:becas@2022.es.pycon.org">becas@2022.es.pycon.org</a>
+y estudiaremos la forma de ayudarle.</p>
+<p>Toda la información la ponemos en los canales oficiales, no olvide suscribirse. Muchas gracias por hacer todo lo posible por acudir a la <strong>PyConES 2022</strong>.</p>Alojamientos y hoteles recomendados 🏨2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/hoteles.html<p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta …</p><p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta con dos fabulosos hoteles 4️⃣ ⭐ bien emplazados en la ciudad y con unas instalaciones estupendas.</p>
+<ul>
+<li>Hotel Abades Nevada Palace</li>
+<li>Hotel Abades Recogidas.</li>
+</ul>
+<p><img alt="abades" src="./images/hoteles/abades.png?style=centerme"></p>
+<p>Aquí el link para hacer la reserva aplicando un descuento exclusivo.</p>
+<p><a href="https://www.abadeshoteles.com/es/?cp=PYCONES2022">RESERVAR CON DESCUENTO</a></p>
+<h2>HC Hotel Center</h2>
+<p>Otra fantástica alternativa es <strong>HC Hotel Center</strong> que cuenta con dos hoteles en la ciudad y nos ofrecen un buen descuento.</p>
+<p>Para disfrutar de un 10% descuento, realiza la reserva desde su página web, seleccionando uno de los hoteles siguiente,
+durante el proceso de reserva introducir el código de descuento.</p>
+<ul>
+<li><a href="https://www.hotelescenter.es/en/hotel-andalucia-center-granada/">Hotel Andalucía Center</a>, introducir el código <strong>229686</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+<li><a href="https://www.hotelescenter.es/es/hotel-granada-center/">Hotel Granada Center</a>, introducir el código <strong>229928</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+</ul>
+<p><img alt="hc" src="./images/hoteles/hc.png?style=centerme"></p>
+<h2>Hotel Palacio de los Navas</h2>
+<p>En el corazón de Granada contamos con <strong>Hotel Palacio de los Navas</strong>, que cuenta con descuentos exclusivos para el evento.
+Es necesario ingresas el código descuento <strong>PYCONES2022</strong> en el proceso de reserva en nuestra web (Es un descuento exclusivo para reservas on line, no válido por teléfono)</p>
+<p>Al introducir el código se obtendrá un descuento del 15% sobre el precio de la habitación doble estándar, doble superior e individual
+El descuento es válido para estancias del 28 septiembre al 4 de Octubre 2020,y estará únicamente operativo hasta el 8 de Agosto (no te duermas 😜)</p>
+<p><a href="https://hotelpalaciodelosnavas.com">We de reservas Hotel Palacio de los Navas</a></p>
+<p><img alt="navas" src="./images/hoteles/navas.png?style=centerme"></p>
+<h2>Room Mate Leo Hotel</h2>
+<p>Room Mate Leo Hotel también quiere colaborar con PyconES GRX ofreciendo un 15% de descuento en sus reservas para el evento.
+Hemos creado este código para las reservas que hagan en Leo a través de nuestra web: <strong>PYCONES</strong>.</p>
+<p><a href="https://room-matehotels.com/">Web de reservas Room Mate Leo</a></p>
+<p><img alt="rommate" src="./images/hoteles/rommate.jpg?style=centerme"></p>
+<h2>Exe Hotels</h2>
+<p>Exe Hotels nos ofrece cercanía y confort, con un código promocional tendremos un descuento del 10% sobre la mejor tarifa disponible en el momento de efectuar la reserva y será válido para las estancias en nuestros hoteles del 30 de septiembre al 02 de octubre de 2022.
+De esta forma, realizando las reservas individualmente, cada asistente podrá elegir el hotel que prefiera y la tipología de habitación que desee.</p>
+<p>Podrán realizarse las reservas hasta 30 días antes de la celebración del evento, y las reservas contarán con una política de cancelación gratuita hasta 14 días antes de la fecha de llegada, después de este periodo, tendrán gastos del 100% de la reserva.</p>
+<p>En el apartado "código promocional" deberán poner el código indicado para que se aplique el descuento, hay que introducir <strong>PYTHON22</strong>.</p>
+<ul>
+<li>Hotel Guadalupe 3* - <a href="mailto:reservas@hotelguadalupe.es">reservas@hotelguadalupe.es</a></li>
+<li>Exe Triunfo Granada 4* - <a href="mailto:reservas@exetriunfogranada.com">reservas@exetriunfogranada.com</a></li>
+<li>Eurostars Puerta Real 4* - <a href="mailto:reservas@eurostarspuertareal.com">reservas@eurostarspuertareal.com</a></li>
+<li>Eurostars Gran Vía 5* - <a href="mailto:reservas@eurostarsgranvia.com">reservas@eurostarsgranvia.com</a></li>
+<li>Áurea Washington Irving 5* - <a href="mailto:reservas@aureawashingtonirving.com">reservas@aureawashingtonirving.com</a></li>
+<li>Áurea Catedral 4* - <a href="mailto:reservas@aureacatedral.com">reservas@aureacatedral.com</a></li>
+</ul>
+<p><a href="https://www.eurostarshotels.com/exe-hotels.html">Web de reservas Exe Hotels</a></p>
+<p><img alt="exe" src="./images/hoteles/exe.png?style=centerme"></p>
+<p>Te esperamos en Granada, un PySaludo.</p>Mapa conferencia y nuestros puntos de interés 🚩2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/mapa.html<p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta …</p><p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta y nos cuentes cuales han sido tus rincones favoritos.</p>
+<p>Como puedes ver marcado en el mapa, la línea 4 del bus urbano y la línea de tranvía son dos buenas opciones para llegar al evento desde muchos puntos de la ciudad.</p>
+<div style="text-align: center">
+<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1IxRp8wgFgWYjOHlw8XLpdZE2sbm_g4Yu&ehbc=2E312F" width="100%" height="700"></iframe>
+<div>Estrenamos nuestro blog ¡Bienvenid@! 🎉2022-01-13T18:20:00+01:002022-01-13T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-13:/welcome.html<p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de …</p><p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de entradas.</p>
+<p>Te esperamos en Granada, un PySaludo.</p>
\ No newline at end of file
diff --git a/feeds/all.atom.xml b/feeds/all.atom.xml
new file mode 100644
index 000000000..c38db2f93
--- /dev/null
+++ b/feeds/all.atom.xml
@@ -0,0 +1,187 @@
+
+PyConES 2022 GRXhttps://2022.es.pycon.org/2022-10-04T18:30:00+02:00Elaboración del programa2022-10-04T18:30:00+02:002022-10-04T18:30:00+02:00Python Españatag:2022.es.pycon.org,2022-10-04:/elaboracion-programa.html<p>Proceso de revisión y elaboración del programa</p><h1>Proceso de revisión y elaboración del programa</h1>
+<p>En toda conferencia donde se admiten propuestas hay al menos dos fases: 1) revisión y 2) elaboración del programa.</p>
+<p>La revisión la lleva a cabo un "comité de expertos/as" que en nuestro caso estará formado por todas las personas que se han ofrecido desinteresadamente entre las comunidades de Python en España. Esta revisión es a ciegas, es decir, el equipo de revisión no sabrá quiénes son las personas que proponen esa charla o taller.</p>
+<p>La elaboración del programa generalmente la hace una persona, pero en nuestro caso lo haremos un grupo reducido de personas de la organización ("comité de programa"). Este grupo seleccionará las charlas y talleres usando las puntuaciones y comentarios de la primera fase e intentará que el programa sea lo más variado (temáticas) y diverso posible.</p>
+<h1>Criterios de revisión</h1>
+<p>Queremos seleccionar las mejores propuestas y que aprendamos un montón en la PyConES. Estos son algunos puntos a considerar durante la revisión:</p>
+<ul>
+<li><em>Relevancia</em>: ¿cómo de relevante es la propuesta para la PyConES y para la temática elegida?</li>
+<li><em>Originalidad</em>: ¿es una propuesta original y diferente que pueda interesar a la audiencia?</li>
+<li><em>Claridad</em>: ¿se entiende bien el título y el resumen de la propuesta?</li>
+<li>¿Crees que la propuesta se puede contar en el <em>tiempo</em> establecido (charla = 30 minutos, taller corto = hasta 2h, taller largo = hasta 4h)?</li>
+</ul>
+<h1>Comité de programa</h1>
+<ul>
+<li><a href="https://www.linkedin.com/in/jimena-eb/">Jimena Bermúdez</a></li>
+<li><a href="https://www.linkedin.com/in/maria-moreno-de-castro/">Dra. María Moreno de Castro</a></li>
+<li><a href="https://www.linkedin.com/in/cmaureir/">Dr. Cristián Maureira-Fredes</a></li>
+<li><a href="https://www.linkedin.com/in/draxus/">Dr. Manuel Martín Salvador</a></li>
+</ul>
+<h1>Equipo de revisión</h1>
+<ul>
+<li>Alberto Fernandez</li>
+<li>Alejandro Almira</li>
+<li>Alicia Pérez Jiménez</li>
+<li>Álvaro Torres Carrasco</li>
+<li>Andrés</li>
+<li>Angela Dini</li>
+<li>Antonio Ramírez</li>
+<li>Carlos Perales</li>
+<li>Carmel</li>
+<li>Christian</li>
+<li>Cristián Maureira-Fredes</li>
+<li>Daniel Domene</li>
+<li>David Arcos</li>
+<li>Diomedes Díaz</li>
+<li>Fernando Salamero</li>
+<li>Héctor Canto</li>
+<li>Ignasi</li>
+<li>Jaime Arrazola</li>
+<li>Javier Cordero</li>
+<li>Jimena</li>
+<li>Jose Alberto Torres Agüera</li>
+<li>Jose Manuel Ortega</li>
+<li>José Miguel López</li>
+<li>Jose Nicolielly Leal</li>
+<li>Juan Antonio Fernández</li>
+<li>Juan Cruz-Benito</li>
+<li>Juan J. Merelo</li>
+<li>Juan José</li>
+<li>Juan Luis Cano Rodríguez</li>
+<li>Kevin Eliezer</li>
+<li>Loris Mularoni</li>
+<li>Manuel Calzado Vidal</li>
+<li>Manuel Martín Salvador</li>
+<li>Maria Moreno de Castro</li>
+<li>Marta Gomez Macias</li>
+<li>Martín Torre Castro</li>
+<li>Martina</li>
+<li>Mateo</li>
+<li>Miguel Hernández</li>
+<li>Miguel Olivares</li>
+<li>Pablo Benavides</li>
+<li>Pablo García</li>
+<li>Paloma de las Cuevas</li>
+<li>Raúl</li>
+<li>Ruben Berenguel</li>
+<li>Ruben Espinosa</li>
+<li>serfer2</li>
+<li>Toni Grabulosa</li>
+<li>Víctor</li>
+<li>Victor Suarez</li>
+<li>Xavi Torelló</li>
+</ul>Taller Django Girls2022-08-23T22:00:00+02:002022-08-23T22:00:00+02:00Python España Orgtag:2022.es.pycon.org,2022-08-23:/taller-djangogirls.html<p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales …</p><p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales utilizan diariamente. ¡Y de una manera muy
+sencilla!</p>
+<p>Será el viernes 30 de Septiembre en la sala Paraninfo del nuevo campos del
+Parque Tecnológico de la Salud de la universidad de Granada, durante el primer
+día de la conferencia nacional de Python más importante de España: PyConES
+2022.</p>
+<p>El taller es gratuito, pero no esperes mucho para apuntarte, las plazas son
+limitadas.</p>
+<p>Si tienes ganas de aprender a programar y tienes un portátil, puedes apuntarte
+a nuestro evento. No necesitas tener ningún conocimiento previo, pero sí mucha
+motivación de adquirirlo rápidamente el día del taller.</p>
+<p>Como asistente al taller, tú podrás:</p>
+<ul>
+<li>Participar en un taller intensivo de Django de un día de duración en el que
+ crearás tu propia página web.</li>
+<li>Disfrutar de un ambiente distendido, en el que aprenderemos casi jugando, con
+ mucho buen rollo, y algún que otro detalle.</li>
+</ul>
+<p>Tenemos un cupo máximo para 30 personas, así que tratá de inscribirte lo antes posible! 😃</p>
+<p><a href="https://djangogirls.org/en/granada/">Inscripciones en el Sitio web de Django Girls</a></p>Becas y ayudas para Pythonistas ❇2022-04-29T14:20:00+02:002022-04-29T14:20:00+02:00Python España Orgtag:2022.es.pycon.org,2022-04-29:/becas.html<p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán …</p><p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán a sufragar los gastos de alojamiento, el transporte y/o la entrada del evento.</p>
+<p>Estas becas están dirigidas a:</p>
+<ul>
+<li>Estudiantes universitarios, formación profesional o BootCamp</li>
+<li>Personas en situación de desempleo.</li>
+<li>Familias monoparentales.</li>
+<li>Personas con contrato de prácticas, jornada reducida o baja laboral.</li>
+</ul>
+<p>Documentación a aportar:</p>
+<ul>
+<li>Estudiantes: presentar el resguardo de la matrícula del curso vigente.</li>
+<li>Desempleados: certificado de desempleo.</li>
+<li>Familias monoparentales: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+<li>Contratos en prácticas, jornada reducida o baja laboral: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+</ul>
+<p>Para solicitar la beca debe rellenar este <a href="https://forms.gle/4sBjJXcGvXnk6PEfA">formulario</a> y tras estudiar la solicitud nos pondremos en contacto vía email con la respuesta.
+<strong>El plazo de postulación es hasta el 15 de Julio.</strong></p>
+<p>Por favor, si tuviera cualquier duda o tuviera una situación compleja y no se viera reflejada en los supuestos contemplados comuníquenoslo a <a href="mailto:becas@2022.es.pycon.org">becas@2022.es.pycon.org</a>
+y estudiaremos la forma de ayudarle.</p>
+<p>Toda la información la ponemos en los canales oficiales, no olvide suscribirse. Muchas gracias por hacer todo lo posible por acudir a la <strong>PyConES 2022</strong>.</p>Alojamientos y hoteles recomendados 🏨2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/hoteles.html<p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta …</p><p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta con dos fabulosos hoteles 4️⃣ ⭐ bien emplazados en la ciudad y con unas instalaciones estupendas.</p>
+<ul>
+<li>Hotel Abades Nevada Palace</li>
+<li>Hotel Abades Recogidas.</li>
+</ul>
+<p><img alt="abades" src="./images/hoteles/abades.png?style=centerme"></p>
+<p>Aquí el link para hacer la reserva aplicando un descuento exclusivo.</p>
+<p><a href="https://www.abadeshoteles.com/es/?cp=PYCONES2022">RESERVAR CON DESCUENTO</a></p>
+<h2>HC Hotel Center</h2>
+<p>Otra fantástica alternativa es <strong>HC Hotel Center</strong> que cuenta con dos hoteles en la ciudad y nos ofrecen un buen descuento.</p>
+<p>Para disfrutar de un 10% descuento, realiza la reserva desde su página web, seleccionando uno de los hoteles siguiente,
+durante el proceso de reserva introducir el código de descuento.</p>
+<ul>
+<li><a href="https://www.hotelescenter.es/en/hotel-andalucia-center-granada/">Hotel Andalucía Center</a>, introducir el código <strong>229686</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+<li><a href="https://www.hotelescenter.es/es/hotel-granada-center/">Hotel Granada Center</a>, introducir el código <strong>229928</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+</ul>
+<p><img alt="hc" src="./images/hoteles/hc.png?style=centerme"></p>
+<h2>Hotel Palacio de los Navas</h2>
+<p>En el corazón de Granada contamos con <strong>Hotel Palacio de los Navas</strong>, que cuenta con descuentos exclusivos para el evento.
+Es necesario ingresas el código descuento <strong>PYCONES2022</strong> en el proceso de reserva en nuestra web (Es un descuento exclusivo para reservas on line, no válido por teléfono)</p>
+<p>Al introducir el código se obtendrá un descuento del 15% sobre el precio de la habitación doble estándar, doble superior e individual
+El descuento es válido para estancias del 28 septiembre al 4 de Octubre 2020,y estará únicamente operativo hasta el 8 de Agosto (no te duermas 😜)</p>
+<p><a href="https://hotelpalaciodelosnavas.com">We de reservas Hotel Palacio de los Navas</a></p>
+<p><img alt="navas" src="./images/hoteles/navas.png?style=centerme"></p>
+<h2>Room Mate Leo Hotel</h2>
+<p>Room Mate Leo Hotel también quiere colaborar con PyconES GRX ofreciendo un 15% de descuento en sus reservas para el evento.
+Hemos creado este código para las reservas que hagan en Leo a través de nuestra web: <strong>PYCONES</strong>.</p>
+<p><a href="https://room-matehotels.com/">Web de reservas Room Mate Leo</a></p>
+<p><img alt="rommate" src="./images/hoteles/rommate.jpg?style=centerme"></p>
+<h2>Exe Hotels</h2>
+<p>Exe Hotels nos ofrece cercanía y confort, con un código promocional tendremos un descuento del 10% sobre la mejor tarifa disponible en el momento de efectuar la reserva y será válido para las estancias en nuestros hoteles del 30 de septiembre al 02 de octubre de 2022.
+De esta forma, realizando las reservas individualmente, cada asistente podrá elegir el hotel que prefiera y la tipología de habitación que desee.</p>
+<p>Podrán realizarse las reservas hasta 30 días antes de la celebración del evento, y las reservas contarán con una política de cancelación gratuita hasta 14 días antes de la fecha de llegada, después de este periodo, tendrán gastos del 100% de la reserva.</p>
+<p>En el apartado "código promocional" deberán poner el código indicado para que se aplique el descuento, hay que introducir <strong>PYTHON22</strong>.</p>
+<ul>
+<li>Hotel Guadalupe 3* - <a href="mailto:reservas@hotelguadalupe.es">reservas@hotelguadalupe.es</a></li>
+<li>Exe Triunfo Granada 4* - <a href="mailto:reservas@exetriunfogranada.com">reservas@exetriunfogranada.com</a></li>
+<li>Eurostars Puerta Real 4* - <a href="mailto:reservas@eurostarspuertareal.com">reservas@eurostarspuertareal.com</a></li>
+<li>Eurostars Gran Vía 5* - <a href="mailto:reservas@eurostarsgranvia.com">reservas@eurostarsgranvia.com</a></li>
+<li>Áurea Washington Irving 5* - <a href="mailto:reservas@aureawashingtonirving.com">reservas@aureawashingtonirving.com</a></li>
+<li>Áurea Catedral 4* - <a href="mailto:reservas@aureacatedral.com">reservas@aureacatedral.com</a></li>
+</ul>
+<p><a href="https://www.eurostarshotels.com/exe-hotels.html">Web de reservas Exe Hotels</a></p>
+<p><img alt="exe" src="./images/hoteles/exe.png?style=centerme"></p>
+<p>Te esperamos en Granada, un PySaludo.</p>Mapa conferencia y nuestros puntos de interés 🚩2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/mapa.html<p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta …</p><p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta y nos cuentes cuales han sido tus rincones favoritos.</p>
+<p>Como puedes ver marcado en el mapa, la línea 4 del bus urbano y la línea de tranvía son dos buenas opciones para llegar al evento desde muchos puntos de la ciudad.</p>
+<div style="text-align: center">
+<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1IxRp8wgFgWYjOHlw8XLpdZE2sbm_g4Yu&ehbc=2E312F" width="100%" height="700"></iframe>
+<div>Estrenamos nuestro blog ¡Bienvenid@! 🎉2022-01-13T18:20:00+01:002022-01-13T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-13:/welcome.html<p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de …</p><p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de entradas.</p>
+<p>Te esperamos en Granada, un PySaludo.</p>
\ No newline at end of file
diff --git a/feeds/alojamiento.atom.xml b/feeds/alojamiento.atom.xml
new file mode 100644
index 000000000..5ecd9b5bd
--- /dev/null
+++ b/feeds/alojamiento.atom.xml
@@ -0,0 +1,52 @@
+
+PyConES 2022 GRX - alojamientohttps://2022.es.pycon.org/2022-01-30T18:20:00+01:00Alojamientos y hoteles recomendados 🏨2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/hoteles.html<p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta …</p><p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta con dos fabulosos hoteles 4️⃣ ⭐ bien emplazados en la ciudad y con unas instalaciones estupendas.</p>
+<ul>
+<li>Hotel Abades Nevada Palace</li>
+<li>Hotel Abades Recogidas.</li>
+</ul>
+<p><img alt="abades" src="./images/hoteles/abades.png?style=centerme"></p>
+<p>Aquí el link para hacer la reserva aplicando un descuento exclusivo.</p>
+<p><a href="https://www.abadeshoteles.com/es/?cp=PYCONES2022">RESERVAR CON DESCUENTO</a></p>
+<h2>HC Hotel Center</h2>
+<p>Otra fantástica alternativa es <strong>HC Hotel Center</strong> que cuenta con dos hoteles en la ciudad y nos ofrecen un buen descuento.</p>
+<p>Para disfrutar de un 10% descuento, realiza la reserva desde su página web, seleccionando uno de los hoteles siguiente,
+durante el proceso de reserva introducir el código de descuento.</p>
+<ul>
+<li><a href="https://www.hotelescenter.es/en/hotel-andalucia-center-granada/">Hotel Andalucía Center</a>, introducir el código <strong>229686</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+<li><a href="https://www.hotelescenter.es/es/hotel-granada-center/">Hotel Granada Center</a>, introducir el código <strong>229928</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+</ul>
+<p><img alt="hc" src="./images/hoteles/hc.png?style=centerme"></p>
+<h2>Hotel Palacio de los Navas</h2>
+<p>En el corazón de Granada contamos con <strong>Hotel Palacio de los Navas</strong>, que cuenta con descuentos exclusivos para el evento.
+Es necesario ingresas el código descuento <strong>PYCONES2022</strong> en el proceso de reserva en nuestra web (Es un descuento exclusivo para reservas on line, no válido por teléfono)</p>
+<p>Al introducir el código se obtendrá un descuento del 15% sobre el precio de la habitación doble estándar, doble superior e individual
+El descuento es válido para estancias del 28 septiembre al 4 de Octubre 2020,y estará únicamente operativo hasta el 8 de Agosto (no te duermas 😜)</p>
+<p><a href="https://hotelpalaciodelosnavas.com">We de reservas Hotel Palacio de los Navas</a></p>
+<p><img alt="navas" src="./images/hoteles/navas.png?style=centerme"></p>
+<h2>Room Mate Leo Hotel</h2>
+<p>Room Mate Leo Hotel también quiere colaborar con PyconES GRX ofreciendo un 15% de descuento en sus reservas para el evento.
+Hemos creado este código para las reservas que hagan en Leo a través de nuestra web: <strong>PYCONES</strong>.</p>
+<p><a href="https://room-matehotels.com/">Web de reservas Room Mate Leo</a></p>
+<p><img alt="rommate" src="./images/hoteles/rommate.jpg?style=centerme"></p>
+<h2>Exe Hotels</h2>
+<p>Exe Hotels nos ofrece cercanía y confort, con un código promocional tendremos un descuento del 10% sobre la mejor tarifa disponible en el momento de efectuar la reserva y será válido para las estancias en nuestros hoteles del 30 de septiembre al 02 de octubre de 2022.
+De esta forma, realizando las reservas individualmente, cada asistente podrá elegir el hotel que prefiera y la tipología de habitación que desee.</p>
+<p>Podrán realizarse las reservas hasta 30 días antes de la celebración del evento, y las reservas contarán con una política de cancelación gratuita hasta 14 días antes de la fecha de llegada, después de este periodo, tendrán gastos del 100% de la reserva.</p>
+<p>En el apartado "código promocional" deberán poner el código indicado para que se aplique el descuento, hay que introducir <strong>PYTHON22</strong>.</p>
+<ul>
+<li>Hotel Guadalupe 3* - <a href="mailto:reservas@hotelguadalupe.es">reservas@hotelguadalupe.es</a></li>
+<li>Exe Triunfo Granada 4* - <a href="mailto:reservas@exetriunfogranada.com">reservas@exetriunfogranada.com</a></li>
+<li>Eurostars Puerta Real 4* - <a href="mailto:reservas@eurostarspuertareal.com">reservas@eurostarspuertareal.com</a></li>
+<li>Eurostars Gran Vía 5* - <a href="mailto:reservas@eurostarsgranvia.com">reservas@eurostarsgranvia.com</a></li>
+<li>Áurea Washington Irving 5* - <a href="mailto:reservas@aureawashingtonirving.com">reservas@aureawashingtonirving.com</a></li>
+<li>Áurea Catedral 4* - <a href="mailto:reservas@aureacatedral.com">reservas@aureacatedral.com</a></li>
+</ul>
+<p><a href="https://www.eurostarshotels.com/exe-hotels.html">Web de reservas Exe Hotels</a></p>
+<p><img alt="exe" src="./images/hoteles/exe.png?style=centerme"></p>
+<p>Te esperamos en Granada, un PySaludo.</p>
\ No newline at end of file
diff --git a/feeds/becas-y-ayudas.atom.xml b/feeds/becas-y-ayudas.atom.xml
new file mode 100644
index 000000000..a841a951c
--- /dev/null
+++ b/feeds/becas-y-ayudas.atom.xml
@@ -0,0 +1,23 @@
+
+PyConES 2022 GRX - becas y ayudashttps://2022.es.pycon.org/2022-04-29T14:20:00+02:00Becas y ayudas para Pythonistas ❇2022-04-29T14:20:00+02:002022-04-29T14:20:00+02:00Python España Orgtag:2022.es.pycon.org,2022-04-29:/becas.html<p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán …</p><p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán a sufragar los gastos de alojamiento, el transporte y/o la entrada del evento.</p>
+<p>Estas becas están dirigidas a:</p>
+<ul>
+<li>Estudiantes universitarios, formación profesional o BootCamp</li>
+<li>Personas en situación de desempleo.</li>
+<li>Familias monoparentales.</li>
+<li>Personas con contrato de prácticas, jornada reducida o baja laboral.</li>
+</ul>
+<p>Documentación a aportar:</p>
+<ul>
+<li>Estudiantes: presentar el resguardo de la matrícula del curso vigente.</li>
+<li>Desempleados: certificado de desempleo.</li>
+<li>Familias monoparentales: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+<li>Contratos en prácticas, jornada reducida o baja laboral: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+</ul>
+<p>Para solicitar la beca debe rellenar este <a href="https://forms.gle/4sBjJXcGvXnk6PEfA">formulario</a> y tras estudiar la solicitud nos pondremos en contacto vía email con la respuesta.
+<strong>El plazo de postulación es hasta el 15 de Julio.</strong></p>
+<p>Por favor, si tuviera cualquier duda o tuviera una situación compleja y no se viera reflejada en los supuestos contemplados comuníquenoslo a <a href="mailto:becas@2022.es.pycon.org">becas@2022.es.pycon.org</a>
+y estudiaremos la forma de ayudarle.</p>
+<p>Toda la información la ponemos en los canales oficiales, no olvide suscribirse. Muchas gracias por hacer todo lo posible por acudir a la <strong>PyConES 2022</strong>.</p>
\ No newline at end of file
diff --git a/feeds/mapa.atom.xml b/feeds/mapa.atom.xml
new file mode 100644
index 000000000..7a4f4cd78
--- /dev/null
+++ b/feeds/mapa.atom.xml
@@ -0,0 +1,16 @@
+
+PyConES 2022 GRX - mapahttps://2022.es.pycon.org/2022-01-30T18:20:00+01:00Mapa conferencia y nuestros puntos de interés 🚩2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/mapa.html<p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta …</p><p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta y nos cuentes cuales han sido tus rincones favoritos.</p>
+<p>Como puedes ver marcado en el mapa, la línea 4 del bus urbano y la línea de tranvía son dos buenas opciones para llegar al evento desde muchos puntos de la ciudad.</p>
+<div style="text-align: center">
+<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1IxRp8wgFgWYjOHlw8XLpdZE2sbm_g4Yu&ehbc=2E312F" width="100%" height="700"></iframe>
+<div>
\ No newline at end of file
diff --git a/feeds/misc.atom.xml b/feeds/misc.atom.xml
new file mode 100644
index 000000000..450bc2d36
--- /dev/null
+++ b/feeds/misc.atom.xml
@@ -0,0 +1,74 @@
+
+PyConES 2022 GRX - mischttps://2022.es.pycon.org/2022-10-04T18:30:00+02:00Elaboración del programa2022-10-04T18:30:00+02:002022-10-04T18:30:00+02:00Python Españatag:2022.es.pycon.org,2022-10-04:/elaboracion-programa.html<p>Proceso de revisión y elaboración del programa</p><h1>Proceso de revisión y elaboración del programa</h1>
+<p>En toda conferencia donde se admiten propuestas hay al menos dos fases: 1) revisión y 2) elaboración del programa.</p>
+<p>La revisión la lleva a cabo un "comité de expertos/as" que en nuestro caso estará formado por todas las personas que se han ofrecido desinteresadamente entre las comunidades de Python en España. Esta revisión es a ciegas, es decir, el equipo de revisión no sabrá quiénes son las personas que proponen esa charla o taller.</p>
+<p>La elaboración del programa generalmente la hace una persona, pero en nuestro caso lo haremos un grupo reducido de personas de la organización ("comité de programa"). Este grupo seleccionará las charlas y talleres usando las puntuaciones y comentarios de la primera fase e intentará que el programa sea lo más variado (temáticas) y diverso posible.</p>
+<h1>Criterios de revisión</h1>
+<p>Queremos seleccionar las mejores propuestas y que aprendamos un montón en la PyConES. Estos son algunos puntos a considerar durante la revisión:</p>
+<ul>
+<li><em>Relevancia</em>: ¿cómo de relevante es la propuesta para la PyConES y para la temática elegida?</li>
+<li><em>Originalidad</em>: ¿es una propuesta original y diferente que pueda interesar a la audiencia?</li>
+<li><em>Claridad</em>: ¿se entiende bien el título y el resumen de la propuesta?</li>
+<li>¿Crees que la propuesta se puede contar en el <em>tiempo</em> establecido (charla = 30 minutos, taller corto = hasta 2h, taller largo = hasta 4h)?</li>
+</ul>
+<h1>Comité de programa</h1>
+<ul>
+<li><a href="https://www.linkedin.com/in/jimena-eb/">Jimena Bermúdez</a></li>
+<li><a href="https://www.linkedin.com/in/maria-moreno-de-castro/">Dra. María Moreno de Castro</a></li>
+<li><a href="https://www.linkedin.com/in/cmaureir/">Dr. Cristián Maureira-Fredes</a></li>
+<li><a href="https://www.linkedin.com/in/draxus/">Dr. Manuel Martín Salvador</a></li>
+</ul>
+<h1>Equipo de revisión</h1>
+<ul>
+<li>Alberto Fernandez</li>
+<li>Alejandro Almira</li>
+<li>Alicia Pérez Jiménez</li>
+<li>Álvaro Torres Carrasco</li>
+<li>Andrés</li>
+<li>Angela Dini</li>
+<li>Antonio Ramírez</li>
+<li>Carlos Perales</li>
+<li>Carmel</li>
+<li>Christian</li>
+<li>Cristián Maureira-Fredes</li>
+<li>Daniel Domene</li>
+<li>David Arcos</li>
+<li>Diomedes Díaz</li>
+<li>Fernando Salamero</li>
+<li>Héctor Canto</li>
+<li>Ignasi</li>
+<li>Jaime Arrazola</li>
+<li>Javier Cordero</li>
+<li>Jimena</li>
+<li>Jose Alberto Torres Agüera</li>
+<li>Jose Manuel Ortega</li>
+<li>José Miguel López</li>
+<li>Jose Nicolielly Leal</li>
+<li>Juan Antonio Fernández</li>
+<li>Juan Cruz-Benito</li>
+<li>Juan J. Merelo</li>
+<li>Juan José</li>
+<li>Juan Luis Cano Rodríguez</li>
+<li>Kevin Eliezer</li>
+<li>Loris Mularoni</li>
+<li>Manuel Calzado Vidal</li>
+<li>Manuel Martín Salvador</li>
+<li>Maria Moreno de Castro</li>
+<li>Marta Gomez Macias</li>
+<li>Martín Torre Castro</li>
+<li>Martina</li>
+<li>Mateo</li>
+<li>Miguel Hernández</li>
+<li>Miguel Olivares</li>
+<li>Pablo Benavides</li>
+<li>Pablo García</li>
+<li>Paloma de las Cuevas</li>
+<li>Raúl</li>
+<li>Ruben Berenguel</li>
+<li>Ruben Espinosa</li>
+<li>serfer2</li>
+<li>Toni Grabulosa</li>
+<li>Víctor</li>
+<li>Victor Suarez</li>
+<li>Xavi Torelló</li>
+</ul>
\ No newline at end of file
diff --git a/feeds/python-espana-org.atom.xml b/feeds/python-espana-org.atom.xml
new file mode 100644
index 000000000..ba151d172
--- /dev/null
+++ b/feeds/python-espana-org.atom.xml
@@ -0,0 +1,115 @@
+
+PyConES 2022 GRX - Python España Orghttps://2022.es.pycon.org/2022-08-23T22:00:00+02:00Taller Django Girls2022-08-23T22:00:00+02:002022-08-23T22:00:00+02:00Python España Orgtag:2022.es.pycon.org,2022-08-23:/taller-djangogirls.html<p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales …</p><p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales utilizan diariamente. ¡Y de una manera muy
+sencilla!</p>
+<p>Será el viernes 30 de Septiembre en la sala Paraninfo del nuevo campos del
+Parque Tecnológico de la Salud de la universidad de Granada, durante el primer
+día de la conferencia nacional de Python más importante de España: PyConES
+2022.</p>
+<p>El taller es gratuito, pero no esperes mucho para apuntarte, las plazas son
+limitadas.</p>
+<p>Si tienes ganas de aprender a programar y tienes un portátil, puedes apuntarte
+a nuestro evento. No necesitas tener ningún conocimiento previo, pero sí mucha
+motivación de adquirirlo rápidamente el día del taller.</p>
+<p>Como asistente al taller, tú podrás:</p>
+<ul>
+<li>Participar en un taller intensivo de Django de un día de duración en el que
+ crearás tu propia página web.</li>
+<li>Disfrutar de un ambiente distendido, en el que aprenderemos casi jugando, con
+ mucho buen rollo, y algún que otro detalle.</li>
+</ul>
+<p>Tenemos un cupo máximo para 30 personas, así que tratá de inscribirte lo antes posible! 😃</p>
+<p><a href="https://djangogirls.org/en/granada/">Inscripciones en el Sitio web de Django Girls</a></p>Becas y ayudas para Pythonistas ❇2022-04-29T14:20:00+02:002022-04-29T14:20:00+02:00Python España Orgtag:2022.es.pycon.org,2022-04-29:/becas.html<p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán …</p><p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán a sufragar los gastos de alojamiento, el transporte y/o la entrada del evento.</p>
+<p>Estas becas están dirigidas a:</p>
+<ul>
+<li>Estudiantes universitarios, formación profesional o BootCamp</li>
+<li>Personas en situación de desempleo.</li>
+<li>Familias monoparentales.</li>
+<li>Personas con contrato de prácticas, jornada reducida o baja laboral.</li>
+</ul>
+<p>Documentación a aportar:</p>
+<ul>
+<li>Estudiantes: presentar el resguardo de la matrícula del curso vigente.</li>
+<li>Desempleados: certificado de desempleo.</li>
+<li>Familias monoparentales: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+<li>Contratos en prácticas, jornada reducida o baja laboral: documento que certifique la situación. Recuerde censurar la información comprometida del documento.</li>
+</ul>
+<p>Para solicitar la beca debe rellenar este <a href="https://forms.gle/4sBjJXcGvXnk6PEfA">formulario</a> y tras estudiar la solicitud nos pondremos en contacto vía email con la respuesta.
+<strong>El plazo de postulación es hasta el 15 de Julio.</strong></p>
+<p>Por favor, si tuviera cualquier duda o tuviera una situación compleja y no se viera reflejada en los supuestos contemplados comuníquenoslo a <a href="mailto:becas@2022.es.pycon.org">becas@2022.es.pycon.org</a>
+y estudiaremos la forma de ayudarle.</p>
+<p>Toda la información la ponemos en los canales oficiales, no olvide suscribirse. Muchas gracias por hacer todo lo posible por acudir a la <strong>PyConES 2022</strong>.</p>Alojamientos y hoteles recomendados 🏨2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/hoteles.html<p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta …</p><p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta con dos fabulosos hoteles 4️⃣ ⭐ bien emplazados en la ciudad y con unas instalaciones estupendas.</p>
+<ul>
+<li>Hotel Abades Nevada Palace</li>
+<li>Hotel Abades Recogidas.</li>
+</ul>
+<p><img alt="abades" src="./images/hoteles/abades.png?style=centerme"></p>
+<p>Aquí el link para hacer la reserva aplicando un descuento exclusivo.</p>
+<p><a href="https://www.abadeshoteles.com/es/?cp=PYCONES2022">RESERVAR CON DESCUENTO</a></p>
+<h2>HC Hotel Center</h2>
+<p>Otra fantástica alternativa es <strong>HC Hotel Center</strong> que cuenta con dos hoteles en la ciudad y nos ofrecen un buen descuento.</p>
+<p>Para disfrutar de un 10% descuento, realiza la reserva desde su página web, seleccionando uno de los hoteles siguiente,
+durante el proceso de reserva introducir el código de descuento.</p>
+<ul>
+<li><a href="https://www.hotelescenter.es/en/hotel-andalucia-center-granada/">Hotel Andalucía Center</a>, introducir el código <strong>229686</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+<li><a href="https://www.hotelescenter.es/es/hotel-granada-center/">Hotel Granada Center</a>, introducir el código <strong>229928</strong> en el formulario de reserva para conseguir 10% de descuento.</li>
+</ul>
+<p><img alt="hc" src="./images/hoteles/hc.png?style=centerme"></p>
+<h2>Hotel Palacio de los Navas</h2>
+<p>En el corazón de Granada contamos con <strong>Hotel Palacio de los Navas</strong>, que cuenta con descuentos exclusivos para el evento.
+Es necesario ingresas el código descuento <strong>PYCONES2022</strong> en el proceso de reserva en nuestra web (Es un descuento exclusivo para reservas on line, no válido por teléfono)</p>
+<p>Al introducir el código se obtendrá un descuento del 15% sobre el precio de la habitación doble estándar, doble superior e individual
+El descuento es válido para estancias del 28 septiembre al 4 de Octubre 2020,y estará únicamente operativo hasta el 8 de Agosto (no te duermas 😜)</p>
+<p><a href="https://hotelpalaciodelosnavas.com">We de reservas Hotel Palacio de los Navas</a></p>
+<p><img alt="navas" src="./images/hoteles/navas.png?style=centerme"></p>
+<h2>Room Mate Leo Hotel</h2>
+<p>Room Mate Leo Hotel también quiere colaborar con PyconES GRX ofreciendo un 15% de descuento en sus reservas para el evento.
+Hemos creado este código para las reservas que hagan en Leo a través de nuestra web: <strong>PYCONES</strong>.</p>
+<p><a href="https://room-matehotels.com/">Web de reservas Room Mate Leo</a></p>
+<p><img alt="rommate" src="./images/hoteles/rommate.jpg?style=centerme"></p>
+<h2>Exe Hotels</h2>
+<p>Exe Hotels nos ofrece cercanía y confort, con un código promocional tendremos un descuento del 10% sobre la mejor tarifa disponible en el momento de efectuar la reserva y será válido para las estancias en nuestros hoteles del 30 de septiembre al 02 de octubre de 2022.
+De esta forma, realizando las reservas individualmente, cada asistente podrá elegir el hotel que prefiera y la tipología de habitación que desee.</p>
+<p>Podrán realizarse las reservas hasta 30 días antes de la celebración del evento, y las reservas contarán con una política de cancelación gratuita hasta 14 días antes de la fecha de llegada, después de este periodo, tendrán gastos del 100% de la reserva.</p>
+<p>En el apartado "código promocional" deberán poner el código indicado para que se aplique el descuento, hay que introducir <strong>PYTHON22</strong>.</p>
+<ul>
+<li>Hotel Guadalupe 3* - <a href="mailto:reservas@hotelguadalupe.es">reservas@hotelguadalupe.es</a></li>
+<li>Exe Triunfo Granada 4* - <a href="mailto:reservas@exetriunfogranada.com">reservas@exetriunfogranada.com</a></li>
+<li>Eurostars Puerta Real 4* - <a href="mailto:reservas@eurostarspuertareal.com">reservas@eurostarspuertareal.com</a></li>
+<li>Eurostars Gran Vía 5* - <a href="mailto:reservas@eurostarsgranvia.com">reservas@eurostarsgranvia.com</a></li>
+<li>Áurea Washington Irving 5* - <a href="mailto:reservas@aureawashingtonirving.com">reservas@aureawashingtonirving.com</a></li>
+<li>Áurea Catedral 4* - <a href="mailto:reservas@aureacatedral.com">reservas@aureacatedral.com</a></li>
+</ul>
+<p><a href="https://www.eurostarshotels.com/exe-hotels.html">Web de reservas Exe Hotels</a></p>
+<p><img alt="exe" src="./images/hoteles/exe.png?style=centerme"></p>
+<p>Te esperamos en Granada, un PySaludo.</p>Mapa conferencia y nuestros puntos de interés 🚩2022-01-30T18:20:00+01:002022-01-30T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-30:/mapa.html<p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta …</p><p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta y nos cuentes cuales han sido tus rincones favoritos.</p>
+<p>Como puedes ver marcado en el mapa, la línea 4 del bus urbano y la línea de tranvía son dos buenas opciones para llegar al evento desde muchos puntos de la ciudad.</p>
+<div style="text-align: center">
+<iframe src="https://www.google.com/maps/d/u/0/embed?mid=1IxRp8wgFgWYjOHlw8XLpdZE2sbm_g4Yu&ehbc=2E312F" width="100%" height="700"></iframe>
+<div>Estrenamos nuestro blog ¡Bienvenid@! 🎉2022-01-13T18:20:00+01:002022-01-13T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-13:/welcome.html<p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de …</p><p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de entradas.</p>
+<p>Te esperamos en Granada, un PySaludo.</p>
\ No newline at end of file
diff --git a/feeds/python-espana-org.rss.xml b/feeds/python-espana-org.rss.xml
new file mode 100644
index 000000000..dbfeb9f8b
--- /dev/null
+++ b/feeds/python-espana-org.rss.xml
@@ -0,0 +1,15 @@
+
+PyConES 2022 GRX - Python España Orghttps://2022.es.pycon.org/Tue, 23 Aug 2022 22:00:00 +0200Taller Django Girlshttps://2022.es.pycon.org/taller-djangogirls.html<p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales …</p>Python España OrgTue, 23 Aug 2022 22:00:00 +0200tag:2022.es.pycon.org,2022-08-23:/taller-djangogirls.htmltalleresBecas y ayudas para Pythonistas ❇https://2022.es.pycon.org/becas.html<p>Desde la comunidad de PyConES queremos facilitar al máximo la asistencia a esta convención de todas las personas que tengan alguna dificultad en este momento y por este motivo se ha decidido dedicar una parte de nuestros recursos en Becas para las personas que lo necesiten.</p>
+<p>Las becas se destinarán …</p>Python España OrgFri, 29 Apr 2022 14:20:00 +0200tag:2022.es.pycon.org,2022-04-29:/becas.htmlbecas y ayudasAlojamientos y hoteles recomendados 🏨https://2022.es.pycon.org/hoteles.html<p>A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.</p>
+<h2>Grupo Abades</h2>
+<p>Tenemos suerte de contar con la participación de <strong>Grupos Abades</strong>. Que cuenta …</p>Python España OrgSun, 30 Jan 2022 18:20:00 +0100tag:2022.es.pycon.org,2022-01-30:/hoteles.htmlalojamientoMapa conferencia y nuestros puntos de interés 🚩https://2022.es.pycon.org/mapa.html<p>Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:</p>
+<p><img alt="mapa conferencia" src="./images/mapa_conferencia.png?style=centerme"></p>
+<p>Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta …</p>Python España OrgSun, 30 Jan 2022 18:20:00 +0100tag:2022.es.pycon.org,2022-01-30:/mapa.htmlmapaEstrenamos nuestro blog ¡Bienvenid@! 🎉https://2022.es.pycon.org/welcome.html<p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de …</p>Python España OrgThu, 13 Jan 2022 18:20:00 +0100tag:2022.es.pycon.org,2022-01-13:/welcome.htmlwelcome
\ No newline at end of file
diff --git a/feeds/python-espana.atom.xml b/feeds/python-espana.atom.xml
new file mode 100644
index 000000000..c1f61c848
--- /dev/null
+++ b/feeds/python-espana.atom.xml
@@ -0,0 +1,74 @@
+
+PyConES 2022 GRX - Python Españahttps://2022.es.pycon.org/2022-10-04T18:30:00+02:00Elaboración del programa2022-10-04T18:30:00+02:002022-10-04T18:30:00+02:00Python Españatag:2022.es.pycon.org,2022-10-04:/elaboracion-programa.html<p>Proceso de revisión y elaboración del programa</p><h1>Proceso de revisión y elaboración del programa</h1>
+<p>En toda conferencia donde se admiten propuestas hay al menos dos fases: 1) revisión y 2) elaboración del programa.</p>
+<p>La revisión la lleva a cabo un "comité de expertos/as" que en nuestro caso estará formado por todas las personas que se han ofrecido desinteresadamente entre las comunidades de Python en España. Esta revisión es a ciegas, es decir, el equipo de revisión no sabrá quiénes son las personas que proponen esa charla o taller.</p>
+<p>La elaboración del programa generalmente la hace una persona, pero en nuestro caso lo haremos un grupo reducido de personas de la organización ("comité de programa"). Este grupo seleccionará las charlas y talleres usando las puntuaciones y comentarios de la primera fase e intentará que el programa sea lo más variado (temáticas) y diverso posible.</p>
+<h1>Criterios de revisión</h1>
+<p>Queremos seleccionar las mejores propuestas y que aprendamos un montón en la PyConES. Estos son algunos puntos a considerar durante la revisión:</p>
+<ul>
+<li><em>Relevancia</em>: ¿cómo de relevante es la propuesta para la PyConES y para la temática elegida?</li>
+<li><em>Originalidad</em>: ¿es una propuesta original y diferente que pueda interesar a la audiencia?</li>
+<li><em>Claridad</em>: ¿se entiende bien el título y el resumen de la propuesta?</li>
+<li>¿Crees que la propuesta se puede contar en el <em>tiempo</em> establecido (charla = 30 minutos, taller corto = hasta 2h, taller largo = hasta 4h)?</li>
+</ul>
+<h1>Comité de programa</h1>
+<ul>
+<li><a href="https://www.linkedin.com/in/jimena-eb/">Jimena Bermúdez</a></li>
+<li><a href="https://www.linkedin.com/in/maria-moreno-de-castro/">Dra. María Moreno de Castro</a></li>
+<li><a href="https://www.linkedin.com/in/cmaureir/">Dr. Cristián Maureira-Fredes</a></li>
+<li><a href="https://www.linkedin.com/in/draxus/">Dr. Manuel Martín Salvador</a></li>
+</ul>
+<h1>Equipo de revisión</h1>
+<ul>
+<li>Alberto Fernandez</li>
+<li>Alejandro Almira</li>
+<li>Alicia Pérez Jiménez</li>
+<li>Álvaro Torres Carrasco</li>
+<li>Andrés</li>
+<li>Angela Dini</li>
+<li>Antonio Ramírez</li>
+<li>Carlos Perales</li>
+<li>Carmel</li>
+<li>Christian</li>
+<li>Cristián Maureira-Fredes</li>
+<li>Daniel Domene</li>
+<li>David Arcos</li>
+<li>Diomedes Díaz</li>
+<li>Fernando Salamero</li>
+<li>Héctor Canto</li>
+<li>Ignasi</li>
+<li>Jaime Arrazola</li>
+<li>Javier Cordero</li>
+<li>Jimena</li>
+<li>Jose Alberto Torres Agüera</li>
+<li>Jose Manuel Ortega</li>
+<li>José Miguel López</li>
+<li>Jose Nicolielly Leal</li>
+<li>Juan Antonio Fernández</li>
+<li>Juan Cruz-Benito</li>
+<li>Juan J. Merelo</li>
+<li>Juan José</li>
+<li>Juan Luis Cano Rodríguez</li>
+<li>Kevin Eliezer</li>
+<li>Loris Mularoni</li>
+<li>Manuel Calzado Vidal</li>
+<li>Manuel Martín Salvador</li>
+<li>Maria Moreno de Castro</li>
+<li>Marta Gomez Macias</li>
+<li>Martín Torre Castro</li>
+<li>Martina</li>
+<li>Mateo</li>
+<li>Miguel Hernández</li>
+<li>Miguel Olivares</li>
+<li>Pablo Benavides</li>
+<li>Pablo García</li>
+<li>Paloma de las Cuevas</li>
+<li>Raúl</li>
+<li>Ruben Berenguel</li>
+<li>Ruben Espinosa</li>
+<li>serfer2</li>
+<li>Toni Grabulosa</li>
+<li>Víctor</li>
+<li>Victor Suarez</li>
+<li>Xavi Torelló</li>
+</ul>
\ No newline at end of file
diff --git a/feeds/python-espana.rss.xml b/feeds/python-espana.rss.xml
new file mode 100644
index 000000000..05d84432d
--- /dev/null
+++ b/feeds/python-espana.rss.xml
@@ -0,0 +1,2 @@
+
+PyConES 2022 GRX - Python Españahttps://2022.es.pycon.org/Tue, 04 Oct 2022 18:30:00 +0200Elaboración del programahttps://2022.es.pycon.org/elaboracion-programa.html<p>Proceso de revisión y elaboración del programa</p>Python EspañaTue, 04 Oct 2022 18:30:00 +0200tag:2022.es.pycon.org,2022-10-04:/elaboracion-programa.htmlmisc
\ No newline at end of file
diff --git a/feeds/talleres.atom.xml b/feeds/talleres.atom.xml
new file mode 100644
index 000000000..3ee83b277
--- /dev/null
+++ b/feeds/talleres.atom.xml
@@ -0,0 +1,29 @@
+
+PyConES 2022 GRX - tallereshttps://2022.es.pycon.org/2022-08-23T22:00:00+02:00Taller Django Girls2022-08-23T22:00:00+02:002022-08-23T22:00:00+02:00Python España Orgtag:2022.es.pycon.org,2022-08-23:/taller-djangogirls.html<p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales …</p><p>¡Queremos animar a más mujeres a dominar la web!</p>
+<p>Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales utilizan diariamente. ¡Y de una manera muy
+sencilla!</p>
+<p>Será el viernes 30 de Septiembre en la sala Paraninfo del nuevo campos del
+Parque Tecnológico de la Salud de la universidad de Granada, durante el primer
+día de la conferencia nacional de Python más importante de España: PyConES
+2022.</p>
+<p>El taller es gratuito, pero no esperes mucho para apuntarte, las plazas son
+limitadas.</p>
+<p>Si tienes ganas de aprender a programar y tienes un portátil, puedes apuntarte
+a nuestro evento. No necesitas tener ningún conocimiento previo, pero sí mucha
+motivación de adquirirlo rápidamente el día del taller.</p>
+<p>Como asistente al taller, tú podrás:</p>
+<ul>
+<li>Participar en un taller intensivo de Django de un día de duración en el que
+ crearás tu propia página web.</li>
+<li>Disfrutar de un ambiente distendido, en el que aprenderemos casi jugando, con
+ mucho buen rollo, y algún que otro detalle.</li>
+</ul>
+<p>Tenemos un cupo máximo para 30 personas, así que tratá de inscribirte lo antes posible! 😃</p>
+<p><a href="https://djangogirls.org/en/granada/">Inscripciones en el Sitio web de Django Girls</a></p>
\ No newline at end of file
diff --git a/feeds/welcome.atom.xml b/feeds/welcome.atom.xml
new file mode 100644
index 000000000..a81b7b347
--- /dev/null
+++ b/feeds/welcome.atom.xml
@@ -0,0 +1,3 @@
+
+PyConES 2022 GRX - welcomehttps://2022.es.pycon.org/2022-01-13T18:20:00+01:00Estrenamos nuestro blog ¡Bienvenid@! 🎉2022-01-13T18:20:00+01:002022-01-13T18:20:00+01:00Python España Orgtag:2022.es.pycon.org,2022-01-13:/welcome.html<p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de …</p><p>Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de entradas.</p>
+<p>Te esperamos en Granada, un PySaludo.</p>
\ No newline at end of file
diff --git a/gallery.html b/gallery.html
new file mode 100644
index 000000000..119d0a937
--- /dev/null
+++ b/gallery.html
@@ -0,0 +1,816 @@
+
+
+
+
+
+
+
+
+
+
+
+ PyConES 2022 GRX - Gallery
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
A continuación te ofrecemos algunos alojamientos con descuento exclusivo para los participantes del evento.
+Estos hoteles se encuentran en Granada localizados cercanos al lugar del evento o a los medios de transporte públicos de la ciudad.
+
Grupo Abades
+
Tenemos suerte de contar con la participación de Grupos Abades. Que cuenta con dos fabulosos hoteles 4️⃣ ⭐ bien emplazados en la ciudad y con unas instalaciones estupendas.
+
+
Hotel Abades Nevada Palace
+
Hotel Abades Recogidas.
+
+
+
Aquí el link para hacer la reserva aplicando un descuento exclusivo.
Otra fantástica alternativa es HC Hotel Center que cuenta con dos hoteles en la ciudad y nos ofrecen un buen descuento.
+
Para disfrutar de un 10% descuento, realiza la reserva desde su página web, seleccionando uno de los hoteles siguiente,
+durante el proceso de reserva introducir el código de descuento.
+
+
Hotel Andalucía Center, introducir el código 229686 en el formulario de reserva para conseguir 10% de descuento.
+
Hotel Granada Center, introducir el código 229928 en el formulario de reserva para conseguir 10% de descuento.
+
+
+
Hotel Palacio de los Navas
+
En el corazón de Granada contamos con Hotel Palacio de los Navas, que cuenta con descuentos exclusivos para el evento.
+Es necesario ingresas el código descuento PYCONES2022 en el proceso de reserva en nuestra web (Es un descuento exclusivo para reservas on line, no válido por teléfono)
+
Al introducir el código se obtendrá un descuento del 15% sobre el precio de la habitación doble estándar, doble superior e individual
+El descuento es válido para estancias del 28 septiembre al 4 de Octubre 2020,y estará únicamente operativo hasta el 8 de Agosto (no te duermas 😜)
Room Mate Leo Hotel también quiere colaborar con PyconES GRX ofreciendo un 15% de descuento en sus reservas para el evento.
+Hemos creado este código para las reservas que hagan en Leo a través de nuestra web: PYCONES.
Exe Hotels nos ofrece cercanía y confort, con un código promocional tendremos un descuento del 10% sobre la mejor tarifa disponible en el momento de efectuar la reserva y será válido para las estancias en nuestros hoteles del 30 de septiembre al 02 de octubre de 2022.
+De esta forma, realizando las reservas individualmente, cada asistente podrá elegir el hotel que prefiera y la tipología de habitación que desee.
+
Podrán realizarse las reservas hasta 30 días antes de la celebración del evento, y las reservas contarán con una política de cancelación gratuita hasta 14 días antes de la fecha de llegada, después de este periodo, tendrán gastos del 100% de la reserva.
+
En el apartado "código promocional" deberán poner el código indicado para que se aplique el descuento, hay que introducir PYTHON22.
Mapa conferencia y algunos puntos de interés que no te puedes perder
+
+
+
+
+
Cronograma del evento ⏲
+
+
+
+
Aquí tienes un cronograma de las etapas por las que irá pasando la PyConEs. Actualizaremos las fechas exactas en breve.
+
Para no perderte un detalle te recomendamos revisar la web, subscribirte a la newsletter y seguirnos por twitter
+
+
+
+
+
+
+
+
+ Inicio
+
+
+
+
+ Enero
+
Os presentamos nuestro fabuloso evento 🎊
+
+
+
+
+
+
Marzo
+
Abrimos envío de propuesta a ponencias ✌
+
+
+
+
+
+
+
Abril
+
Apertura venta de entradas 🛒
+
+
+
+
+
+
Junio
+
Revisamos las ponencias enviadas 🔍
+
+
+
+
+
+
Julio
+
Notificamos las ponencias aceptadas 🎸
+
+
+
+
+
+
+
+
+
30 Septiembre
+
Recepción y primera jornada del evento
+
+
+
+
+
+
+
+
+
1 Octubre
+
Segunda jornada del evento
+
+
+
+
+
+
+
+
2 Octubre
+
Tercera jornada del evento y clausura
+
+
+
+ Fin
+
+
+
+
+
+
+
Localización 🗺
+
Se celebrará en el nuevo campus universitario ubicado en el Parque Tecnológico de la Salud,
+Dirección: Av. de la Ilustración, 80, 18016 Granada
+
+
+
+
+
+
+
+
+
+
+
+
Mapa del espacio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Patrocinios 🏢
+
+ Gracias a las empresas que colaboran con la PyConES
+ podemos ofrecer el mejor evento y experiencia posible.
+
+
+ Somos una conferencia con un bajo coste de entrada
+ capaz de ofrecer una experiencia de 3 días incluyendo
+ regalos, almuerzos, comidas y meriendas.
+
+
+ Además contamos con servicio de guardería, becas y
+ traducción en directo a lenguaje de signos
+ para que nadie se quede fuera.
+
+
+ Con la ayuda de estas empresas conseguimos
+ hacer un evento diverso e inclusivo enfocado
+ en cuidar la comunidad de Python.
+
+ Experta en Sistemas de Diálogo o Interfaces conversacionales (chatbots y asistentes de texto y voz) desde 2009
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mapa.html b/mapa.html
new file mode 100644
index 000000000..af0f1a86d
--- /dev/null
+++ b/mapa.html
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+ Mapa conferencia y nuestros puntos de interés 🚩 - PyConES 2022 GRX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Mapa conferencia y nuestros puntos de interés 🚩
+
+
+
Acá puedes encontrar un mapa de los edificios donde se realizará
+la conferencia:
+
+
Aquí dejamos algunos (de los muchísimos) puntos de interés que podría interesarte visitar en los días que dure tu estancia.
+Si la frenética agenda del evento lo permite 😜.
+También te invitamos a que investigues por tu cuenta y nos cuentes cuales han sido tus rincones favoritos.
+
Como puedes ver marcado en el mapa, la línea 4 del bus urbano y la línea de tranvía son dos buenas opciones para llegar al evento desde muchos puntos de la ciudad.
Python España, como asociación en torno a la que se organizan eventos de
+distintos tipos, quiere asegurar que todas las personas que participen
+en dichos eventos o comunicaciones tengan una experiencia profesional y
+positiva de aprendizaje, colaboración u ocio. Para ello, se espera que
+quien participe en la comunidad muestre respeto y cortesía hacia el
+resto.
+
Al participar en la comunidad de Python España, te comprometes a
+fomentar una experiencia libre de acoso para todo el mundo,
+independientemente de la edad, dimensión corporal, discapacidad visible
+o invisible, etnicidad, características sexuales, identidad y expresión
+de género, nivel de experiencia, educación, nivel socio-económico,
+nacionalidad, apariencia personal, raza, religión, o identidad u
+orientación sexual.
+
Este Código de Conducta detalla qué comportamientos se esperan, cuáles
+se rechazan y qué mecanismos hay para ayudar a una persona que esté
+siendo objeto de comportamientos inadecuados.
+
¿Por qué un código de conducta?
+
Siguiendo con el zen de Python, explícito mejor que implícito.
+Explicitar qué se espera del ambiente en cualquier evento de Python
+España:
+
+
Favorece que más personas sepan que son bienvenidas
+
Evita ambigüedades
+
Construye un clima de confianza, donde si alguien quiere reportar un incidente, sabrá que no empezaremos por cuestionarle (victim blaming).
+
+
Alcance
+
Este código de conducta es aplicable a todas las personas que participen
+en espacios de la comunidad de Python España, ya sean en línea o
+presenciales. También se aplica a espacios públicos donde una persona
+esté en representación de la comunidad. Ejemplos de esto último incluyen
+el uso de la cuenta oficial de correo electrónico, publicaciones a
+través de las redes sociales oficiales, o presentaciones con personas
+designadas en eventos en línea o no.
+
Nuestros estándares
+
Ejemplos de comportamiento que contribuyen a crear un ambiente positivo
+para nuestra comunidad:
+
+
Demostrar empatía y amabilidad ante otras personas. No insultes o
+ humilles a otros asistentes. Recuerda que las bromas sexistas,
+ racistas o discriminatorias no son apropiadas. Nunca lo son
+
Respetar las diferentes opiniones, puntos de vista y experiencias
+
Dar y aceptar adecuadamente críticas constructivas
+
Aceptar la responsabilidad y disculparse ante quienes se vean
+ afectados por nuestros errores, aprendiendo de la experiencia
+
Centrarse en lo que sea mejor no sólo para nosotros como individuos,
+ sino para la comunidad en general
+
Usar un lenguaje inclusivo y que dé cabida a una audiencia diversa
+
Prestar especial atención a las personas que recién llegan a la
+ comunidad
+
Presentarte con tus pronombres y preguntarle a otra persona los suyos
+ para que exista una comunicación clara y sin sesgo
+
+
Ejemplos de comportamiento inaceptable:
+
+
El uso de lenguaje o imágenes sexualizadas, y aproximaciones o
+ atenciones sexuales de cualquier tipo
+
Comentarios despectivos trolling, insultantes o
+ derogatorios, y ataques personales o políticos
+
Bromas racistas, sexistas o excluyentes
+
El acoso en público o privado
+
Publicar información privada de otras personas, tales como
+ direcciones físicas o de correo electrónico, sin su permiso
+ explícito
+
Otras conductas que puedan ser razonablemente consideradas como
+ inapropiadas en un entorno profesional
+
+
Por acoso se entiende comentarios ofensivos relacionados con género,
+orientación sexual, discapacidad, apariencia física, tamaño corporal,
+etnia o religión, pornografía en espacios públicos, intimidación
+deliberada, acecho, persecución, acoso por fotografías o grabaciones,
+constante interrupción de charlas u otros eventos, contacto físico
+inapropiado y atención sexual no deseada.
+
Cumplimiento
+
La administración de la comunidad es responsable de aclarar y hacer
+cumplir este código de conducta; en caso de que se determine un
+comportamiento inadecuado, tomará las acciones que considere oportunas.
+Éstas van desde exigir el cese del comportamiento, hasta la expulsión de
+una persona de un evento o de la Asociación, sin derecho a reembolso. La
+administración de la comunidad tendrá el derecho y la responsabilidad de
+eliminar, editar o rechazar mensajes, comentarios,
+código, ediciones de páginas de wiki, tickets y otras contribuciones que
+no se alineen con este código de conducta, y comunicará las razones para
+sus decisiones de moderación cuando sea apropiado.
+
Denuncia e información de contacto
+
Los casos de comportamiento abusivo, acosador o inaceptable de otro modo
+podrán ser denunciados a las personas administradoras de la comunidad
+responsables del cumplimiento:
+
+
Si es un evento presencial, ponte en contacto directamente con las
+ personas organizadoras del evento. Es muy probable que hayan
+ publicando un código de conducta específico del evento con
+ instrucciones de a quién acudir; te proporcionarán un espacio seguro
+ para ayudarte.
+
Si se trata de un espacio en línea, ponte en contacto con las
+ personas moderadoras de ese espacio.
+
En el foro de Discourse puedes denunciar publicaciones individuales
+ o contactar con el grupo de
+ moderadores.
+
En Telegram, a la persona propietaria del grupo u otras
+ administradoras.
+
Para otros espacios o de forma alternativa, ponte en contacto con la
+ Junta Directiva en contacto@es.python.org.
+
+
Todas las personas administradoras de la comunidad están obligadas a
+respetar la privacidad y la seguridad de quienes denuncien incidentes.
+
Atribución
+
Este código de conducta extiende el ya existente con aportaciones de
+otros códigos:
+
+
+
+
\ No newline at end of file
diff --git a/pages/granada.html b/pages/granada.html
new file mode 100644
index 000000000..debd81ee4
--- /dev/null
+++ b/pages/granada.html
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+
+
+
+
+ Granada, ciudad del congreso 🏢 - PyConES 2022 GRX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Granada, ciudad del congreso 🏢
+
+
+
Granada, ciudad de congresos 🏢
+
Granada es una ciudad ideal para la organización de un evento como este. Conocida a nivel
+mundial por una de las maravillas arquitectónicas de la humanidad, la Alhambra, es destino
+turístico de millones de personas. No obstante Granada cuenta con otros atractivos en la
+provincia como son Sierra Nevada a una media hora de la capital, sus costas a 45 minutos y
+una variedad de barrios con arquitecturas impresionantes, museos y espacios culturales que la
+hacen ser una de las pocas ciudades que combina a la perfección un gran patrimonio ecológico
+y una gran riqueza cultural.
+
Además, se trata de una ciudad universitaria, donde se alojan cerca de 60.000 estudiantes
+de grado y postgrado, lo que hace que sea una ciudad muy activa y dinámica, con multitud de
+actividades y ambientes donde la juventud es protagonista. Este espíritu joven y dinámico
+es el que la hace que Granada sea el destino favorito entre estudiantes Erasmus: una
+ciudad especial que todo el mundo debería conocer. Sus ofertas de ocio son perfectas para
+albergar a una gran variedad de personas, donde cada una encontrará lo que más se adecúe a
+sus necesidades y gustos.
+
La oferta de hoteles en Granada es amplísima, desde los más baratos y acogedores a los
+más sofisticados y lujosos acorde con la demanda de la ciudad. La ciudad ofrece desde 2015
+más de 50.000 plazas hoteleras disponibles para todos los gustos y bolsillos. Desde la
+organización estamos trabajando para conseguir descuentos especiales en hoteles.
+
Es de destacar que la ciudad cuenta con numerosos espacios de ocio en los que disfrutar de
+las distintas experiencias que se ofrecen. Todas esas opciones están recopiladas y actualizadas
+cada semana en diferentes guías culturales online, como son
+Granada Cultura o
+Agenda Granada donde se incluyen
+planes musicales, gastronómicos, para disfrutar en familia, programación de teatros, museos,
+cines y exposiciones. La amplitud de la oferta garantiza una experiencia en la ciudad agradable
+para cualquier tipo de visitante.
+
A Granada por tierra, mar o aire
+
Granada es una ciudad con accesibilidad a través de varios medios de transporte.
+
Por tierra: carreteras y red ferroviaria 🚆
+
+
+
Red de carreteras: Granada se encuentra comunicada con el resto de Andalucía
+gracias a la A-92, y con el resto de la península a través de la A-44 que enlaza
+directamente con la E5 hacia Madrid, al norte o con la A7 en la zona de la costa
+granadina. También posee otras vías menos importantes, pero que facilitan la
+comunicación con el este de la península, como es la N-432 que enlaza directamente
+Granada con Extremadura.
+
+
+
Autobuses: La comunicación mediante autobús/autocar es una opción de coste bajo y
+que se presenta con multitud de comunicaciones diarias entre gran cantidad de ciudades
+de la península, principalmente bien comunicado con Madrid, Sevilla y Málaga.
+
+
+
Red ferroviaria: Granada está conectada a las diferentes
+líneas de alta velocidad españolas, y mantiene conexiones diarias a Madrid, Barcelona y Málaga,
+además de otros puntos intermedios como Córdoba, Ciudad Real,
+Zaragoza, Tarragona o Lérida. Utilizando también estas conexiones a Madrid y
+Barcelona, son accesibles otros muchos destinos a través de AVE, como son todas las
+estaciones AVE del norte y este del territorio nacional. Disponemos de un 5% de descuento en
+trayectos de RENFE. Escríbenos a contacto@2022.es.pycon.org y te explicaremos cómo canjearlo.
+
+
+
Por aire: viajar en avión ✈️
+
La ciudad cuenta con el Aeropuerto Federico García Lorca Granada-Jaén, situado a 24 km
+del centro de Granada, que conecta directamente la ciudad con Madrid, Barcelona, Bilbao, Gran
+Canaria, Palma de Mallorca y Tenerife. Según la época del año, también se operan vuelos
+directos con Londres, París, Manchester, Berlín y Milán.
+
Además, la proximidad de la ciudad al Aeropuerto Internacional de Málaga (130 km) la hace
+una ciudad próxima a otras partes del mundo y, por supuesto, a las principales capitales de
+provincia españolas con una mayor frecuencia, existiendo comunicación desde el centro de
+Granada hasta el aeropuerto de Málaga directamente, con una frecuencia de seis veces al día y
+un coste aproximado de entre 12 y 15 euros. Este servicio se complementa ofertando, en
+algunos de los horarios disponibles, un servicio que recoge viajeros en diferentes puntos de la
+ciudad y los transporta directamente hasta las diferentes terminales del aeropuerto.
+
Por mar: barcos desde el puerto de Motril ⛵
+
El puerto de Motril (a 65 km de la capital granadina) conecta Granada con Melilla, Nador y Al
+Hoceima. Esta conexión, facilita la participación de ciudades cercanas a Granada y con una
+gran relación con ella, como es por ejemplo Melilla. Melilla es una capital de provincia gran
+olvidada en muchas ocasiones, y que es puerta para mercados internacionales en Marruecos y
+Argelia, donde algunas empresas participantes podrían extender sus mercados.
+
Campus de la Salud - Paraninfo - Universidad de Granada 🏛
+
Está situado en una de las zonas más accesibles de la ciudad, por su cercanía a la
+ronda de circunvalación de la ciudad (A44) y a la vez, es posible desplazarse a pie, en bus o
+en metro desde ella hasta el centro de la ciudad. Este campus de la salud es parte
+importante del Parque Tecnológico de la Salud situado en el barrio de El Zaidín de la
+capital granadina. Esta zona se está convirtiendo en la alternativa más viable para
+un campus no saturado por tráfico y ruidos.
+
+
Está formado por dos facultades y un complejo de servicios como biblioteca,
+comedores, etc. con instalaciones nuevas y abiertas al exterior incluyendo zonas verdes,
+cafeterías o centros comerciales a una mínima distancia. Este campus es el que se
+utiliza habitualmente en la Universidad de Granada para los grandes eventos, como la
+recepción de futuros nuevos estudiantes, que se celebra en mayo o sede de congresos de
+cualquier carácter como congresos médicos, farmaceúticos o de cualquier otro tipo.
+
La celebración de la PyConES en este espacio incluye el uso de los comedores universitarios.
+Respecto al aparcamiento, todas las calles adyacentes son de aparcamiento libre y existe un
+gran parking, ubicado en el complejo hospitalario existente junto a la facultad de medicina, que
+ofrece plazas de aparcamiento previo pago (pero no es precisamente barato). Sin embargo, solo a
+una parada de metro, existe un parking gratuito en el centro comercial Nevada Shopping con 4000 plazas disponibles que puede usarse
+sin coste alguno, por lo que es mucho más barato aparcar en esta superficie y coger el metro
+hasta el Campus de la Salud que pagar el día completo en el parking del complejo hospitalario.
+ Ingeniera/investigadora en Deepmind y doctora en Informática.
+
+
+
+
+
La era del diamante: sesgos y riesgos en IA 🤖
+
En la más pura tradición valenciana, esta charla no es una charla, es una falla.
+Un monumento a todo lo que brilla pero está hueco, un monumento a todo lo hay que quemar en AI.
+
Mai Giménez (ella/la) tiene un doctorado en informática y un máster en diseño e ilustración por la Universidad Politécnica de València. Ha investigado sobre procesamiento del lenguaje natural y diseño colaborativo. Actualmente trabaja en DeepMind investigando agentes generalistas.
+
Divulgadora y co-organizadora de comunidades. Mai ha formado parte de la junta de Python España y colaborado con la organización de diversas ediciones de PyConES y PyLondinium, es PSF Fellow y forma parte de PyLadies.
+
Mai trabaja activamente para mejorar la diversidad, equidad e inclusión en la tecnología y en la ciencia. Y en su tiempo libre boxea y abraza animalitos.
Nieves Ábalos, co-fundadora y Chief Product Officer, de Monoceros Labs, un Estudio de Innovación especializado en estrategia, diseño conversacional y tecnologías del habla.
+
Han creado apps de voz para Amazon Alexa como Veo Veo, Pasapalabra (Atresmedia) o Diana (Grupo Planeta).
+Estudió Ingeniería Informática en la UGR, y lleva más de 10 años en el sector de las interfaces conversacionales.
+
Con Máster en Desarrollo de Software, estuvo investigando en sistemas de diálogo como parte de su formación pre-doctoral en el grupo SISDIAL.
+Trabajó 5 años en el departamento de innovación de BEEVA, empresa del grupo BBVA, donde llegó a ser Product Manager de una plataforma conversacional con Agentes Inteligentes.
+
Fue directora del Máster de Inteligencia Artificial y Deep Learning de Kschool en 2020, además de profesora del módulo de Interfaces Conversacionales.
+Además de dar charlas y clases de Interfaces de Voz en diferentes escuelas de diseño y negocio, desde 2019 lidera la comunidad Women in Voice Spain, parte de Women in Voice.
+
En 2020, fue reconocida como Alexa Champion, mérito que dan desde Amazon Alexa a los desarrolladores que más aportan a la comunidad.
+ Gracias a las empresas que colaboran con la PyConES
+ podemos ofrecer el mejor evento y experiencia posible.
+
+
+ Somos una conferencia con un bajo coste de entrada
+ capaz de ofrecer una experiencia de 3 días incluyendo
+ regalos, almuerzos, comidas y meriendas.
+
+
+ Además contamos con servicio de guardería, becas y
+ traducción en directo a lenguaje de signos
+ para que nadie se quede fuera.
+
+
+ Con la ayuda de estas empresas conseguimos
+ hacer un evento diverso e inclusivo enfocado
+ en cuidar la comunidad de Python.
+
Si eres una mujer y quieres aprender a hacer páginas web, tenemos buenas
+noticias para ti!. Estamos organizando un taller en el cual podrás construir tu
+propio sitio web desde cero en un día. Utilizaremos todas las herramientas que
+programadores profesionales utilizan diariamente. ¡Y de una manera muy
+sencilla!
+
Será el viernes 30 de Septiembre en la sala Paraninfo del nuevo campos del
+Parque Tecnológico de la Salud de la universidad de Granada, durante el primer
+día de la conferencia nacional de Python más importante de España: PyConES
+2022.
+
El taller es gratuito, pero no esperes mucho para apuntarte, las plazas son
+limitadas.
+
Si tienes ganas de aprender a programar y tienes un portátil, puedes apuntarte
+a nuestro evento. No necesitas tener ningún conocimiento previo, pero sí mucha
+motivación de adquirirlo rápidamente el día del taller.
+
Como asistente al taller, tú podrás:
+
+
Participar en un taller intensivo de Django de un día de duración en el que
+ crearás tu propia página web.
+
Disfrutar de un ambiente distendido, en el que aprenderemos casi jugando, con
+ mucho buen rollo, y algún que otro detalle.
+
+
Tenemos un cupo máximo para 30 personas, así que tratá de inscribirte lo antes posible! 😃
elements work best with integers. round up to ensure contents fits
+ }
+ function getSectionHasLiquidHeight(props, sectionConfig) {
+ return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
+ }
+ function getAllowYScrolling(props, sectionConfig) {
+ return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
+ getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
+ }
+ // TODO: ONLY use `arg`. force out internal function to use same API
+ function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) {
+ var expandRows = arg.expandRows;
+ var content = typeof chunkConfig.content === 'function' ?
+ chunkConfig.content(arg) :
+ createElement('table', {
+ role: 'presentation',
+ className: [
+ chunkConfig.tableClassName,
+ sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
+ ].join(' '),
+ style: {
+ minWidth: arg.tableMinWidth,
+ width: arg.clientWidth,
+ height: expandRows ? arg.clientHeight : '', // css `height` on a
serves as a min-height
+ },
+ }, arg.tableColGroupNode, createElement(isHeader ? 'thead' : 'tbody', {
+ role: 'presentation',
+ }, typeof chunkConfig.rowContent === 'function'
+ ? chunkConfig.rowContent(arg)
+ : chunkConfig.rowContent));
+ return content;
+ }
+ function isColPropsEqual(cols0, cols1) {
+ return isArraysEqual(cols0, cols1, isPropsEqual);
+ }
+ function renderMicroColGroup(cols, shrinkWidth) {
+ var colNodes = [];
+ /*
+ for ColProps with spans, it would have been great to make a single
+ HOWEVER, Chrome was getting messing up distributing the width to
/
elements with colspans.
+ SOLUTION: making individual
elements makes Chrome behave.
+ */
+ for (var _i = 0, cols_1 = cols; _i < cols_1.length; _i++) {
+ var colProps = cols_1[_i];
+ var span = colProps.span || 1;
+ for (var i = 0; i < span; i += 1) {
+ colNodes.push(createElement("col", { style: {
+ width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
+ minWidth: colProps.minWidth || '',
+ } }));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray(['colgroup', {}], colNodes));
+ }
+ function sanitizeShrinkWidth(shrinkWidth) {
+ /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
+ 4 accounts for 2 2-pixel borders. TODO: better solution? */
+ return shrinkWidth == null ? 4 : shrinkWidth;
+ }
+ function hasShrinkWidth(cols) {
+ for (var _i = 0, cols_2 = cols; _i < cols_2.length; _i++) {
+ var col = cols_2[_i];
+ if (col.width === 'shrink') {
+ return true;
+ }
+ }
+ return false;
+ }
+ function getScrollGridClassNames(liquid, context) {
+ var classNames = [
+ 'fc-scrollgrid',
+ context.theme.getClass('table'),
+ ];
+ if (liquid) {
+ classNames.push('fc-scrollgrid-liquid');
+ }
+ return classNames;
+ }
+ function getSectionClassNames(sectionConfig, wholeTableVGrow) {
+ var classNames = [
+ 'fc-scrollgrid-section',
+ "fc-scrollgrid-section-" + sectionConfig.type,
+ sectionConfig.className, // used?
+ ];
+ if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
+ classNames.push('fc-scrollgrid-section-liquid');
+ }
+ if (sectionConfig.isSticky) {
+ classNames.push('fc-scrollgrid-section-sticky');
+ }
+ return classNames;
+ }
+ function renderScrollShim(arg) {
+ return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: {
+ width: arg.clientWidth,
+ minWidth: arg.tableMinWidth,
+ } }));
+ }
+ function getStickyHeaderDates(options) {
+ var stickyHeaderDates = options.stickyHeaderDates;
+ if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
+ stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyHeaderDates;
+ }
+ function getStickyFooterScrollbar(options) {
+ var stickyFooterScrollbar = options.stickyFooterScrollbar;
+ if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
+ stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
+ }
+ return stickyFooterScrollbar;
+ }
+
+ var SimpleScrollGrid = /** @class */ (function (_super) {
+ __extends(SimpleScrollGrid, _super);
+ function SimpleScrollGrid() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.processCols = memoize(function (a) { return a; }, isColPropsEqual); // so we get same `cols` props every time
+ // yucky to memoize VNodes, but much more efficient for consumers
+ _this.renderMicroColGroup = memoize(renderMicroColGroup);
+ _this.scrollerRefs = new RefMap();
+ _this.scrollerElRefs = new RefMap(_this._handleScrollerEl.bind(_this));
+ _this.state = {
+ shrinkWidth: null,
+ forceYScrollbars: false,
+ scrollerClientWidths: {},
+ scrollerClientHeights: {},
+ };
+ // TODO: can do a really simple print-view. dont need to join rows
+ _this.handleSizing = function () {
+ _this.setState(__assign({ shrinkWidth: _this.computeShrinkWidth() }, _this.computeScrollerDims()));
+ };
+ return _this;
+ }
+ SimpleScrollGrid.prototype.render = function () {
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var sectionConfigs = props.sections || [];
+ var cols = this.processCols(props.cols);
+ var microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
+ var classNames = getScrollGridClassNames(props.liquid, context);
+ if (props.collapsibleWidth) {
+ classNames.push('fc-scrollgrid-collapsible');
+ }
+ // TODO: make DRY
+ var configCnt = sectionConfigs.length;
+ var configI = 0;
+ var currentConfig;
+ var headSectionNodes = [];
+ var bodySectionNodes = [];
+ var footSectionNodes = [];
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
+ headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
+ bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false));
+ configI += 1;
+ }
+ while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
+ footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
+ configI += 1;
+ }
+ // firefox bug: when setting height on table and there is a thead or tfoot,
+ // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
+ // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
+ // if so, use a simpler dom structure, jam everything into a lone tbody.
+ var isBuggy = !getCanVGrowWithinCell();
+ var roleAttrs = { role: 'rowgroup' };
+ return createElement('table', {
+ role: 'grid',
+ className: classNames.join(' '),
+ style: { height: props.height },
+ }, Boolean(!isBuggy && headSectionNodes.length) && createElement.apply(void 0, __spreadArray(['thead', roleAttrs], headSectionNodes)), Boolean(!isBuggy && bodySectionNodes.length) && createElement.apply(void 0, __spreadArray(['tbody', roleAttrs], bodySectionNodes)), Boolean(!isBuggy && footSectionNodes.length) && createElement.apply(void 0, __spreadArray(['tfoot', roleAttrs], footSectionNodes)), isBuggy && createElement.apply(void 0, __spreadArray(__spreadArray(__spreadArray(['tbody', roleAttrs], headSectionNodes), bodySectionNodes), footSectionNodes)));
+ };
+ SimpleScrollGrid.prototype.renderSection = function (sectionConfig, microColGroupNode, isHeader) {
+ if ('outerContent' in sectionConfig) {
+ return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent));
+ }
+ return (createElement("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader)));
+ };
+ SimpleScrollGrid.prototype.renderChunkTd = function (sectionConfig, microColGroupNode, chunkConfig, isHeader) {
+ if ('outerContent' in chunkConfig) {
+ return chunkConfig.outerContent;
+ }
+ var props = this.props;
+ var _a = this.state, forceYScrollbars = _a.forceYScrollbars, scrollerClientWidths = _a.scrollerClientWidths, scrollerClientHeights = _a.scrollerClientHeights;
+ var needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
+ var isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
+ // for `!props.liquid` - is WHOLE scrollgrid natural height?
+ // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
+ var overflowY = !props.liquid ? 'visible' :
+ forceYScrollbars ? 'scroll' :
+ !needsYScrolling ? 'hidden' :
+ 'auto';
+ var sectionKey = sectionConfig.key;
+ var content = renderChunkContent(sectionConfig, chunkConfig, {
+ tableColGroupNode: microColGroupNode,
+ tableMinWidth: '',
+ clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
+ clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
+ expandRows: sectionConfig.expandRows,
+ syncRowHeights: false,
+ rowSyncHeights: [],
+ reportRowHeightChange: function () { },
+ }, isHeader);
+ return createElement(isHeader ? 'th' : 'td', {
+ ref: chunkConfig.elRef,
+ role: 'presentation',
+ }, createElement("div", { className: "fc-scroller-harness" + (isLiquid ? ' fc-scroller-harness-liquid' : '') },
+ createElement(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
+ : true }, content)));
+ };
+ SimpleScrollGrid.prototype._handleScrollerEl = function (scrollerEl, key) {
+ var section = getSectionByKey(this.props.sections, key);
+ if (section) {
+ setRef(section.chunk.scrollerElRef, scrollerEl);
+ }
+ };
+ SimpleScrollGrid.prototype.componentDidMount = function () {
+ this.handleSizing();
+ this.context.addResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.componentDidUpdate = function () {
+ // TODO: need better solution when state contains non-sizing things
+ this.handleSizing();
+ };
+ SimpleScrollGrid.prototype.componentWillUnmount = function () {
+ this.context.removeResizeHandler(this.handleSizing);
+ };
+ SimpleScrollGrid.prototype.computeShrinkWidth = function () {
+ return hasShrinkWidth(this.props.cols)
+ ? computeShrinkWidth(this.scrollerElRefs.getAll())
+ : 0;
+ };
+ SimpleScrollGrid.prototype.computeScrollerDims = function () {
+ var scrollbarWidth = getScrollbarWidths();
+ var _a = this, scrollerRefs = _a.scrollerRefs, scrollerElRefs = _a.scrollerElRefs;
+ var forceYScrollbars = false;
+ var scrollerClientWidths = {};
+ var scrollerClientHeights = {};
+ for (var sectionKey in scrollerRefs.currentMap) {
+ var scroller = scrollerRefs.currentMap[sectionKey];
+ if (scroller && scroller.needsYScrolling()) {
+ forceYScrollbars = true;
+ break;
+ }
+ }
+ for (var _i = 0, _b = this.props.sections; _i < _b.length; _i++) {
+ var section = _b[_i];
+ var sectionKey = section.key;
+ var scrollerEl = scrollerElRefs.currentMap[sectionKey];
+ if (scrollerEl) {
+ var harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
+ scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
+ ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
+ : 0));
+ scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
+ }
+ }
+ return { forceYScrollbars: forceYScrollbars, scrollerClientWidths: scrollerClientWidths, scrollerClientHeights: scrollerClientHeights };
+ };
+ return SimpleScrollGrid;
+ }(BaseComponent));
+ SimpleScrollGrid.addStateEquality({
+ scrollerClientWidths: isPropsEqual,
+ scrollerClientHeights: isPropsEqual,
+ });
+ function getSectionByKey(sections, key) {
+ for (var _i = 0, sections_1 = sections; _i < sections_1.length; _i++) {
+ var section = sections_1[_i];
+ if (section.key === key) {
+ return section;
+ }
+ }
+ return null;
+ }
+
+ var EventRoot = /** @class */ (function (_super) {
+ __extends(EventRoot, _super);
+ function EventRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.elRef = createRef();
+ return _this;
+ }
+ EventRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var seg = props.seg;
+ var eventRange = seg.eventRange;
+ var ui = eventRange.ui;
+ var hookProps = {
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ view: context.viewApi,
+ timeText: props.timeText,
+ textColor: ui.textColor,
+ backgroundColor: ui.backgroundColor,
+ borderColor: ui.borderColor,
+ isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
+ isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
+ isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
+ isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
+ isStart: Boolean(seg.isStart),
+ isEnd: Boolean(seg.isEnd),
+ isPast: Boolean(props.isPast),
+ isFuture: Boolean(props.isFuture),
+ isToday: Boolean(props.isToday),
+ isSelected: Boolean(props.isSelected),
+ isDragging: Boolean(props.isDragging),
+ isResizing: Boolean(props.isResizing),
+ };
+ var standardClassNames = getEventClassNames(hookProps).concat(ui.classNames);
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.eventClassNames, content: options.eventContent, defaultContent: props.defaultContent, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, elRef: this.elRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, standardClassNames.concat(customClassNames), innerElRef, innerContent, hookProps); }));
+ };
+ EventRoot.prototype.componentDidMount = function () {
+ setElSeg(this.elRef.current, this.props.seg);
+ };
+ /*
+ need to re-assign seg to the element if seg changes, even if the element is the same
+ */
+ EventRoot.prototype.componentDidUpdate = function (prevProps) {
+ var seg = this.props.seg;
+ if (seg !== prevProps.seg) {
+ setElSeg(this.elRef.current, seg);
+ }
+ };
+ return EventRoot;
+ }(BaseComponent));
+
+ // should not be a purecomponent
+ var StandardEvent = /** @class */ (function (_super) {
+ __extends(StandardEvent, _super);
+ function StandardEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ StandardEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var seg = props.seg;
+ var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat;
+ var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent$6, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: {
+ borderColor: hookProps.borderColor,
+ backgroundColor: hookProps.backgroundColor,
+ }, ref: rootElRef }, getSegAnchorAttrs(seg, context)),
+ createElement("div", { className: "fc-event-main", ref: innerElRef, style: { color: hookProps.textColor } }, innerContent),
+ hookProps.isStartResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-start" }),
+ hookProps.isEndResizable &&
+ createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))); }));
+ };
+ return StandardEvent;
+ }(BaseComponent));
+ function renderInnerContent$6(innerProps) {
+ return (createElement("div", { className: "fc-event-main-frame" },
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title-container" },
+ createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))));
+ }
+
+ var NowIndicatorRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var options = context.options;
+ var hookProps = {
+ isAxis: props.isAxis,
+ date: context.dateEnv.toDate(props.date),
+ view: context.viewApi,
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.nowIndicatorClassNames, content: options.nowIndicatorContent, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children));
+ })); };
+
+ var DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
+ var DayCellContent = /** @class */ (function (_super) {
+ __extends(DayCellContent, _super);
+ function DayCellContent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ DayCellContent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = refineDayCellHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ return (createElement(ContentHook, { hookProps: hookProps, content: options.dayCellContent, defaultContent: props.defaultContent }, props.children));
+ };
+ return DayCellContent;
+ }(BaseComponent));
+ function refineDayCellHookProps(raw) {
+ var date = raw.date, dateEnv = raw.dateEnv;
+ var dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile);
+ return __assign(__assign(__assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraProps);
+ }
+
+ var DayCellRoot = /** @class */ (function (_super) {
+ __extends(DayCellRoot, _super);
+ function DayCellRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.refineHookProps = memoizeObjArg(refineDayCellHookProps);
+ _this.normalizeClassNames = buildClassNameNormalizer();
+ return _this;
+ }
+ DayCellRoot.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var options = context.options;
+ var hookProps = this.refineHookProps({
+ date: props.date,
+ dateProfile: props.dateProfile,
+ todayRange: props.todayRange,
+ showDayNumber: props.showDayNumber,
+ extraProps: props.extraHookProps,
+ viewApi: context.viewApi,
+ dateEnv: context.dateEnv,
+ });
+ var classNames = getDayClassNames(hookProps, context.theme).concat(hookProps.isDisabled
+ ? [] // don't use custom classNames if disabled
+ : this.normalizeClassNames(options.dayCellClassNames, hookProps));
+ var dataAttrs = hookProps.isDisabled ? {} : {
+ 'data-date': formatDayString(props.date),
+ };
+ return (createElement(MountHook, { hookProps: hookProps, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, classNames, dataAttrs, hookProps.isDisabled); }));
+ };
+ return DayCellRoot;
+ }(BaseComponent));
+
+ function renderFill(fillType) {
+ return (createElement("div", { className: "fc-" + fillType }));
+ }
+ var BgEvent = function (props) { return (createElement(EventRoot, { defaultContent: renderInnerContent$5, seg: props.seg /* uselesss i think */, timeText: "", disableDragging: true, disableResizing: true, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("div", { ref: rootElRef, className: ['fc-bg-event'].concat(classNames).join(' '), style: {
+ backgroundColor: hookProps.backgroundColor,
+ } }, innerContent)); })); };
+ function renderInnerContent$5(props) {
+ var title = props.event.title;
+ return title && (createElement("div", { className: "fc-event-title" }, props.event.title));
+ }
+
+ var WeekNumberRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) {
+ var dateEnv = context.dateEnv, options = context.options;
+ var date = props.date;
+ var format = options.weekNumberFormat || props.defaultFormat;
+ var num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
+ var text = dateEnv.format(date, format);
+ var hookProps = { num: num, text: text, date: date };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.weekNumberClassNames, content: options.weekNumberContent, defaultContent: renderInner, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children));
+ })); };
+ function renderInner(innerProps) {
+ return innerProps.text;
+ }
+
+ var PADDING_FROM_VIEWPORT = 10;
+ var Popover = /** @class */ (function (_super) {
+ __extends(Popover, _super);
+ function Popover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.state = {
+ titleId: getUniqueDomId(),
+ };
+ _this.handleRootEl = function (el) {
+ _this.rootEl = el;
+ if (_this.props.elRef) {
+ setRef(_this.props.elRef, el);
+ }
+ };
+ // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
+ _this.handleDocumentMouseDown = function (ev) {
+ // only hide the popover if the click happened outside the popover
+ var target = getEventTargetViaRoot(ev);
+ if (!_this.rootEl.contains(target)) {
+ _this.handleCloseClick();
+ }
+ };
+ _this.handleDocumentKeyDown = function (ev) {
+ if (ev.key === 'Escape') {
+ _this.handleCloseClick();
+ }
+ };
+ _this.handleCloseClick = function () {
+ var onClose = _this.props.onClose;
+ if (onClose) {
+ onClose();
+ }
+ };
+ return _this;
+ }
+ Popover.prototype.render = function () {
+ var _a = this.context, theme = _a.theme, options = _a.options;
+ var _b = this, props = _b.props, state = _b.state;
+ var classNames = [
+ 'fc-popover',
+ theme.getClass('popover'),
+ ].concat(props.extraClassNames || []);
+ return createPortal(createElement("div", __assign({ id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId }, props.extraAttrs, { ref: this.handleRootEl }),
+ createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
+ createElement("span", { className: "fc-popover-title", id: state.titleId }, props.title),
+ createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })),
+ createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
+ };
+ Popover.prototype.componentDidMount = function () {
+ document.addEventListener('mousedown', this.handleDocumentMouseDown);
+ document.addEventListener('keydown', this.handleDocumentKeyDown);
+ this.updateSize();
+ };
+ Popover.prototype.componentWillUnmount = function () {
+ document.removeEventListener('mousedown', this.handleDocumentMouseDown);
+ document.removeEventListener('keydown', this.handleDocumentKeyDown);
+ };
+ Popover.prototype.updateSize = function () {
+ var isRtl = this.context.isRtl;
+ var _a = this.props, alignmentEl = _a.alignmentEl, alignGridTop = _a.alignGridTop;
+ var rootEl = this.rootEl;
+ var alignmentRect = computeClippedClientRect(alignmentEl);
+ if (alignmentRect) {
+ var popoverDims = rootEl.getBoundingClientRect();
+ // position relative to viewport
+ var popoverTop = alignGridTop
+ ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
+ : alignmentRect.top;
+ var popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
+ // constrain
+ popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
+ popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
+ popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
+ var origin_1 = rootEl.offsetParent.getBoundingClientRect();
+ applyStyle(rootEl, {
+ top: popoverTop - origin_1.top,
+ left: popoverLeft - origin_1.left,
+ });
+ }
+ };
+ return Popover;
+ }(BaseComponent));
+
+ var MorePopover = /** @class */ (function (_super) {
+ __extends(MorePopover, _super);
+ function MorePopover() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ useEventCenter: false,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ MorePopover.prototype.render = function () {
+ var _a = this.context, options = _a.options, dateEnv = _a.dateEnv;
+ var props = this.props;
+ var startDate = props.startDate, todayRange = props.todayRange, dateProfile = props.dateProfile;
+ var title = dateEnv.format(startDate, options.dayPopoverFormat);
+ return (createElement(DayCellRoot, { date: startDate, dateProfile: dateProfile, todayRange: todayRange, elRef: this.handleRootEl }, function (rootElRef, dayClassNames, dataAttrs) { return (createElement(Popover, { elRef: rootElRef, id: props.id, title: title, extraClassNames: ['fc-more-popover'].concat(dayClassNames), extraAttrs: dataAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
+ createElement(DayCellContent, { date: startDate, dateProfile: dateProfile, todayRange: todayRange }, function (innerElRef, innerContent) { return (innerContent &&
+ createElement("div", { className: "fc-more-popover-misc", ref: innerElRef }, innerContent)); }),
+ props.children)); }));
+ };
+ MorePopover.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) {
+ var _a = this, rootEl = _a.rootEl, props = _a.props;
+ if (positionLeft >= 0 && positionLeft < elWidth &&
+ positionTop >= 0 && positionTop < elHeight) {
+ return {
+ dateProfile: props.dateProfile,
+ dateSpan: __assign({ allDay: true, range: {
+ start: props.startDate,
+ end: props.endDate,
+ } }, props.extraDateSpan),
+ dayEl: rootEl,
+ rect: {
+ left: 0,
+ top: 0,
+ right: elWidth,
+ bottom: elHeight,
+ },
+ layer: 1, // important when comparing with hits from other components
+ };
+ }
+ return null;
+ };
+ return MorePopover;
+ }(DateComponent));
+
+ var MoreLinkRoot = /** @class */ (function (_super) {
+ __extends(MoreLinkRoot, _super);
+ function MoreLinkRoot() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.linkElRef = createRef();
+ _this.state = {
+ isPopoverOpen: false,
+ popoverId: getUniqueDomId(),
+ };
+ _this.handleClick = function (ev) {
+ var _a = _this, props = _a.props, context = _a.context;
+ var moreLinkClick = context.options.moreLinkClick;
+ var date = computeRange(props).start;
+ function buildPublicSeg(seg) {
+ var _a = seg.eventRange, def = _a.def, instance = _a.instance, range = _a.range;
+ return {
+ event: new EventApi(context, def, instance),
+ start: context.dateEnv.toDate(range.start),
+ end: context.dateEnv.toDate(range.end),
+ isStart: seg.isStart,
+ isEnd: seg.isEnd,
+ };
+ }
+ if (typeof moreLinkClick === 'function') {
+ moreLinkClick = moreLinkClick({
+ date: date,
+ allDay: Boolean(props.allDayDate),
+ allSegs: props.allSegs.map(buildPublicSeg),
+ hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
+ jsEvent: ev,
+ view: context.viewApi,
+ });
+ }
+ if (!moreLinkClick || moreLinkClick === 'popover') {
+ _this.setState({ isPopoverOpen: true });
+ }
+ else if (typeof moreLinkClick === 'string') { // a view name
+ context.calendarApi.zoomTo(date, moreLinkClick);
+ }
+ };
+ _this.handlePopoverClose = function () {
+ _this.setState({ isPopoverOpen: false });
+ };
+ return _this;
+ }
+ MoreLinkRoot.prototype.render = function () {
+ var _this = this;
+ var _a = this, props = _a.props, state = _a.state;
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ var viewApi = context.viewApi, options = context.options, calendarApi = context.calendarApi;
+ var moreLinkText = options.moreLinkText;
+ var moreCnt = props.moreCnt;
+ var range = computeRange(props);
+ var text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
+ ? moreLinkText.call(calendarApi, moreCnt)
+ : "+" + moreCnt + " " + moreLinkText;
+ var title = formatWithOrdinals(options.moreLinkHint, [moreCnt], text);
+ var hookProps = {
+ num: moreCnt,
+ shortText: "+" + moreCnt,
+ text: text,
+ view: viewApi,
+ };
+ return (createElement(Fragment, null,
+ Boolean(props.moreCnt) && (createElement(RenderHook, { elRef: _this.linkElRef, hookProps: hookProps, classNames: options.moreLinkClassNames, content: options.moreLinkContent, defaultContent: props.defaultContent || renderMoreLinkInner$1, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, ['fc-more-link'].concat(customClassNames), innerElRef, innerContent, _this.handleClick, title, state.isPopoverOpen, state.isPopoverOpen ? state.popoverId : ''); })),
+ state.isPopoverOpen && (createElement(MorePopover, { id: state.popoverId, startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: _this.parentEl, alignmentEl: props.alignmentElRef.current, alignGridTop: props.alignGridTop, onClose: _this.handlePopoverClose }, props.popoverContent()))));
+ }));
+ };
+ MoreLinkRoot.prototype.componentDidMount = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.componentDidUpdate = function () {
+ this.updateParentEl();
+ };
+ MoreLinkRoot.prototype.updateParentEl = function () {
+ if (this.linkElRef.current) {
+ this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness');
+ }
+ };
+ return MoreLinkRoot;
+ }(BaseComponent));
+ function renderMoreLinkInner$1(props) {
+ return props.text;
+ }
+ function computeRange(props) {
+ if (props.allDayDate) {
+ return {
+ start: props.allDayDate,
+ end: addDays(props.allDayDate, 1),
+ };
+ }
+ var hiddenSegs = props.hiddenSegs;
+ return {
+ start: computeEarliestSegStart(hiddenSegs),
+ end: computeLatestSegEnd(hiddenSegs),
+ };
+ }
+ function computeEarliestSegStart(segs) {
+ return segs.reduce(pickEarliestStart).eventRange.range.start;
+ }
+ function pickEarliestStart(seg0, seg1) {
+ return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
+ }
+ function computeLatestSegEnd(segs) {
+ return segs.reduce(pickLatestEnd).eventRange.range.end;
+ }
+ function pickLatestEnd(seg0, seg1) {
+ return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
+ }
+
+ // exports
+ // --------------------------------------------------------------------------------------------------
+ var version = '5.10.1'; // important to type it, so .d.ts has generic string
+
+ var Calendar = /** @class */ (function (_super) {
+ __extends(Calendar, _super);
+ function Calendar(el, optionOverrides) {
+ if (optionOverrides === void 0) { optionOverrides = {}; }
+ var _this = _super.call(this) || this;
+ _this.isRendering = false;
+ _this.isRendered = false;
+ _this.currentClassNames = [];
+ _this.customContentRenderId = 0; // will affect custom generated classNames?
+ _this.handleAction = function (action) {
+ // actions we know we want to render immediately
+ switch (action.type) {
+ case 'SET_EVENT_DRAG':
+ case 'SET_EVENT_RESIZE':
+ _this.renderRunner.tryDrain();
+ }
+ };
+ _this.handleData = function (data) {
+ _this.currentData = data;
+ _this.renderRunner.request(data.calendarOptions.rerenderDelay);
+ };
+ _this.handleRenderRequest = function () {
+ if (_this.isRendering) {
+ _this.isRendered = true;
+ var currentData_1 = _this.currentData;
+ render(createElement(CalendarRoot, { options: currentData_1.calendarOptions, theme: currentData_1.theme, emitter: currentData_1.emitter }, function (classNames, height, isHeightAuto, forPrint) {
+ _this.setClassNames(classNames);
+ _this.setHeight(height);
+ return (createElement(CustomContentRenderContext.Provider, { value: _this.customContentRenderId },
+ createElement(CalendarContent, __assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData_1))));
+ }), _this.el);
+ }
+ else if (_this.isRendered) {
+ _this.isRendered = false;
+ unmountComponentAtNode(_this.el);
+ _this.setClassNames([]);
+ _this.setHeight('');
+ }
+ flushToDom();
+ };
+ _this.el = el;
+ _this.renderRunner = new DelayedRunner(_this.handleRenderRequest);
+ new CalendarDataManager({
+ optionOverrides: optionOverrides,
+ calendarApi: _this,
+ onAction: _this.handleAction,
+ onData: _this.handleData,
+ });
+ return _this;
+ }
+ Object.defineProperty(Calendar.prototype, "view", {
+ get: function () { return this.currentData.viewApi; } // for public API
+ ,
+ enumerable: false,
+ configurable: true
+ });
+ Calendar.prototype.render = function () {
+ var wasRendering = this.isRendering;
+ if (!wasRendering) {
+ this.isRendering = true;
+ }
+ else {
+ this.customContentRenderId += 1;
+ }
+ this.renderRunner.request();
+ if (wasRendering) {
+ this.updateSize();
+ }
+ };
+ Calendar.prototype.destroy = function () {
+ if (this.isRendering) {
+ this.isRendering = false;
+ this.renderRunner.request();
+ }
+ };
+ Calendar.prototype.updateSize = function () {
+ _super.prototype.updateSize.call(this);
+ flushToDom();
+ };
+ Calendar.prototype.batchRendering = function (func) {
+ this.renderRunner.pause('batchRendering');
+ func();
+ this.renderRunner.resume('batchRendering');
+ };
+ Calendar.prototype.pauseRendering = function () {
+ this.renderRunner.pause('pauseRendering');
+ };
+ Calendar.prototype.resumeRendering = function () {
+ this.renderRunner.resume('pauseRendering', true);
+ };
+ Calendar.prototype.resetOptions = function (optionOverrides, append) {
+ this.currentDataManager.resetOptions(optionOverrides, append);
+ };
+ Calendar.prototype.setClassNames = function (classNames) {
+ if (!isArraysEqual(classNames, this.currentClassNames)) {
+ var classList = this.el.classList;
+ for (var _i = 0, _a = this.currentClassNames; _i < _a.length; _i++) {
+ var className = _a[_i];
+ classList.remove(className);
+ }
+ for (var _b = 0, classNames_1 = classNames; _b < classNames_1.length; _b++) {
+ var className = classNames_1[_b];
+ classList.add(className);
+ }
+ this.currentClassNames = classNames;
+ }
+ };
+ Calendar.prototype.setHeight = function (height) {
+ applyStyleProp(this.el, 'height', height);
+ };
+ return Calendar;
+ }(CalendarApi));
+
+ config.touchMouseIgnoreWait = 500;
+ var ignoreMouseDepth = 0;
+ var listenerCnt = 0;
+ var isWindowTouchMoveCancelled = false;
+ /*
+ Uses a "pointer" abstraction, which monitors UI events for both mouse and touch.
+ Tracks when the pointer "drags" on a certain element, meaning down+move+up.
+
+ Also, tracks if there was touch-scrolling.
+ Also, can prevent touch-scrolling from happening.
+ Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement.
+
+ emits:
+ - pointerdown
+ - pointermove
+ - pointerup
+ */
+ var PointerDragging = /** @class */ (function () {
+ function PointerDragging(containerEl) {
+ var _this = this;
+ this.subjectEl = null;
+ // options that can be directly assigned by caller
+ this.selector = ''; // will cause subjectEl in all emitted events to be this element
+ this.handleSelector = '';
+ this.shouldIgnoreMove = false;
+ this.shouldWatchScroll = true; // for simulating pointermove on scroll
+ // internal states
+ this.isDragging = false;
+ this.isTouchDragging = false;
+ this.wasTouchScroll = false;
+ // Mouse
+ // ----------------------------------------------------------------------------------------------------
+ this.handleMouseDown = function (ev) {
+ if (!_this.shouldIgnoreMouse() &&
+ isPrimaryMouseButton(ev) &&
+ _this.tryStart(ev)) {
+ var pev = _this.createEventFromMouse(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ if (!_this.shouldIgnoreMove) {
+ document.addEventListener('mousemove', _this.handleMouseMove);
+ }
+ document.addEventListener('mouseup', _this.handleMouseUp);
+ }
+ };
+ this.handleMouseMove = function (ev) {
+ var pev = _this.createEventFromMouse(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleMouseUp = function (ev) {
+ document.removeEventListener('mousemove', _this.handleMouseMove);
+ document.removeEventListener('mouseup', _this.handleMouseUp);
+ _this.emitter.trigger('pointerup', _this.createEventFromMouse(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ };
+ // Touch
+ // ----------------------------------------------------------------------------------------------------
+ this.handleTouchStart = function (ev) {
+ if (_this.tryStart(ev)) {
+ _this.isTouchDragging = true;
+ var pev = _this.createEventFromTouch(ev, true);
+ _this.emitter.trigger('pointerdown', pev);
+ _this.initScrollWatch(pev);
+ // unlike mouse, need to attach to target, not document
+ // https://stackoverflow.com/a/45760014
+ var targetEl = ev.target;
+ if (!_this.shouldIgnoreMove) {
+ targetEl.addEventListener('touchmove', _this.handleTouchMove);
+ }
+ targetEl.addEventListener('touchend', _this.handleTouchEnd);
+ targetEl.addEventListener('touchcancel', _this.handleTouchEnd); // treat it as a touch end
+ // attach a handler to get called when ANY scroll action happens on the page.
+ // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
+ // http://stackoverflow.com/a/32954565/96342
+ window.addEventListener('scroll', _this.handleTouchScroll, true);
+ }
+ };
+ this.handleTouchMove = function (ev) {
+ var pev = _this.createEventFromTouch(ev);
+ _this.recordCoords(pev);
+ _this.emitter.trigger('pointermove', pev);
+ };
+ this.handleTouchEnd = function (ev) {
+ if (_this.isDragging) { // done to guard against touchend followed by touchcancel
+ var targetEl = ev.target;
+ targetEl.removeEventListener('touchmove', _this.handleTouchMove);
+ targetEl.removeEventListener('touchend', _this.handleTouchEnd);
+ targetEl.removeEventListener('touchcancel', _this.handleTouchEnd);
+ window.removeEventListener('scroll', _this.handleTouchScroll, true); // useCaptured=true
+ _this.emitter.trigger('pointerup', _this.createEventFromTouch(ev));
+ _this.cleanup(); // call last so that pointerup has access to props
+ _this.isTouchDragging = false;
+ startIgnoringMouse();
+ }
+ };
+ this.handleTouchScroll = function () {
+ _this.wasTouchScroll = true;
+ };
+ this.handleScroll = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ var pageX = (window.pageXOffset - _this.prevScrollX) + _this.prevPageX;
+ var pageY = (window.pageYOffset - _this.prevScrollY) + _this.prevPageY;
+ _this.emitter.trigger('pointermove', {
+ origEvent: ev,
+ isTouch: _this.isTouchDragging,
+ subjectEl: _this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: pageX - _this.origPageX,
+ deltaY: pageY - _this.origPageY,
+ });
+ }
+ };
+ this.containerEl = containerEl;
+ this.emitter = new Emitter();
+ containerEl.addEventListener('mousedown', this.handleMouseDown);
+ containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerCreated();
+ }
+ PointerDragging.prototype.destroy = function () {
+ this.containerEl.removeEventListener('mousedown', this.handleMouseDown);
+ this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
+ listenerDestroyed();
+ };
+ PointerDragging.prototype.tryStart = function (ev) {
+ var subjectEl = this.querySubjectEl(ev);
+ var downEl = ev.target;
+ if (subjectEl &&
+ (!this.handleSelector || elementClosest(downEl, this.handleSelector))) {
+ this.subjectEl = subjectEl;
+ this.isDragging = true; // do this first so cancelTouchScroll will work
+ this.wasTouchScroll = false;
+ return true;
+ }
+ return false;
+ };
+ PointerDragging.prototype.cleanup = function () {
+ isWindowTouchMoveCancelled = false;
+ this.isDragging = false;
+ this.subjectEl = null;
+ // keep wasTouchScroll around for later access
+ this.destroyScrollWatch();
+ };
+ PointerDragging.prototype.querySubjectEl = function (ev) {
+ if (this.selector) {
+ return elementClosest(ev.target, this.selector);
+ }
+ return this.containerEl;
+ };
+ PointerDragging.prototype.shouldIgnoreMouse = function () {
+ return ignoreMouseDepth || this.isTouchDragging;
+ };
+ // can be called by user of this class, to cancel touch-based scrolling for the current drag
+ PointerDragging.prototype.cancelTouchScroll = function () {
+ if (this.isDragging) {
+ isWindowTouchMoveCancelled = true;
+ }
+ };
+ // Scrolling that simulates pointermoves
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.initScrollWatch = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.recordCoords(ev);
+ window.addEventListener('scroll', this.handleScroll, true); // useCapture=true
+ }
+ };
+ PointerDragging.prototype.recordCoords = function (ev) {
+ if (this.shouldWatchScroll) {
+ this.prevPageX = ev.pageX;
+ this.prevPageY = ev.pageY;
+ this.prevScrollX = window.pageXOffset;
+ this.prevScrollY = window.pageYOffset;
+ }
+ };
+ PointerDragging.prototype.destroyScrollWatch = function () {
+ if (this.shouldWatchScroll) {
+ window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true
+ }
+ };
+ // Event Normalization
+ // ----------------------------------------------------------------------------------------------------
+ PointerDragging.prototype.createEventFromMouse = function (ev, isFirst) {
+ var deltaX = 0;
+ var deltaY = 0;
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = ev.pageX;
+ this.origPageY = ev.pageY;
+ }
+ else {
+ deltaX = ev.pageX - this.origPageX;
+ deltaY = ev.pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: false,
+ subjectEl: this.subjectEl,
+ pageX: ev.pageX,
+ pageY: ev.pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ PointerDragging.prototype.createEventFromTouch = function (ev, isFirst) {
+ var touches = ev.touches;
+ var pageX;
+ var pageY;
+ var deltaX = 0;
+ var deltaY = 0;
+ // if touch coords available, prefer,
+ // because FF would give bad ev.pageX ev.pageY
+ if (touches && touches.length) {
+ pageX = touches[0].pageX;
+ pageY = touches[0].pageY;
+ }
+ else {
+ pageX = ev.pageX;
+ pageY = ev.pageY;
+ }
+ // TODO: repeat code
+ if (isFirst) {
+ this.origPageX = pageX;
+ this.origPageY = pageY;
+ }
+ else {
+ deltaX = pageX - this.origPageX;
+ deltaY = pageY - this.origPageY;
+ }
+ return {
+ origEvent: ev,
+ isTouch: true,
+ subjectEl: this.subjectEl,
+ pageX: pageX,
+ pageY: pageY,
+ deltaX: deltaX,
+ deltaY: deltaY,
+ };
+ };
+ return PointerDragging;
+ }());
+ // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
+ function isPrimaryMouseButton(ev) {
+ return ev.button === 0 && !ev.ctrlKey;
+ }
+ // Ignoring fake mouse events generated by touch
+ // ----------------------------------------------------------------------------------------------------
+ function startIgnoringMouse() {
+ ignoreMouseDepth += 1;
+ setTimeout(function () {
+ ignoreMouseDepth -= 1;
+ }, config.touchMouseIgnoreWait);
+ }
+ // We want to attach touchmove as early as possible for Safari
+ // ----------------------------------------------------------------------------------------------------
+ function listenerCreated() {
+ listenerCnt += 1;
+ if (listenerCnt === 1) {
+ window.addEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function listenerDestroyed() {
+ listenerCnt -= 1;
+ if (!listenerCnt) {
+ window.removeEventListener('touchmove', onWindowTouchMove, { passive: false });
+ }
+ }
+ function onWindowTouchMove(ev) {
+ if (isWindowTouchMoveCancelled) {
+ ev.preventDefault();
+ }
+ }
+
+ /*
+ An effect in which an element follows the movement of a pointer across the screen.
+ The moving element is a clone of some other element.
+ Must call start + handleMove + stop.
+ */
+ var ElementMirror = /** @class */ (function () {
+ function ElementMirror() {
+ this.isVisible = false; // must be explicitly enabled
+ this.sourceEl = null;
+ this.mirrorEl = null;
+ this.sourceElRect = null; // screen coords relative to viewport
+ // options that can be set directly by caller
+ this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues
+ this.zIndex = 9999;
+ this.revertDuration = 0;
+ }
+ ElementMirror.prototype.start = function (sourceEl, pageX, pageY) {
+ this.sourceEl = sourceEl;
+ this.sourceElRect = this.sourceEl.getBoundingClientRect();
+ this.origScreenX = pageX - window.pageXOffset;
+ this.origScreenY = pageY - window.pageYOffset;
+ this.deltaX = 0;
+ this.deltaY = 0;
+ this.updateElPosition();
+ };
+ ElementMirror.prototype.handleMove = function (pageX, pageY) {
+ this.deltaX = (pageX - window.pageXOffset) - this.origScreenX;
+ this.deltaY = (pageY - window.pageYOffset) - this.origScreenY;
+ this.updateElPosition();
+ };
+ // can be called before start
+ ElementMirror.prototype.setIsVisible = function (bool) {
+ if (bool) {
+ if (!this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = '';
+ }
+ this.isVisible = bool; // needs to happen before updateElPosition
+ this.updateElPosition(); // because was not updating the position while invisible
+ }
+ }
+ else if (this.isVisible) {
+ if (this.mirrorEl) {
+ this.mirrorEl.style.display = 'none';
+ }
+ this.isVisible = bool;
+ }
+ };
+ // always async
+ ElementMirror.prototype.stop = function (needsRevertAnimation, callback) {
+ var _this = this;
+ var done = function () {
+ _this.cleanup();
+ callback();
+ };
+ if (needsRevertAnimation &&
+ this.mirrorEl &&
+ this.isVisible &&
+ this.revertDuration && // if 0, transition won't work
+ (this.deltaX || this.deltaY) // if same coords, transition won't work
+ ) {
+ this.doRevertAnimation(done, this.revertDuration);
+ }
+ else {
+ setTimeout(done, 0);
+ }
+ };
+ ElementMirror.prototype.doRevertAnimation = function (callback, revertDuration) {
+ var mirrorEl = this.mirrorEl;
+ var finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened
+ mirrorEl.style.transition =
+ 'top ' + revertDuration + 'ms,' +
+ 'left ' + revertDuration + 'ms';
+ applyStyle(mirrorEl, {
+ left: finalSourceElRect.left,
+ top: finalSourceElRect.top,
+ });
+ whenTransitionDone(mirrorEl, function () {
+ mirrorEl.style.transition = '';
+ callback();
+ });
+ };
+ ElementMirror.prototype.cleanup = function () {
+ if (this.mirrorEl) {
+ removeElement(this.mirrorEl);
+ this.mirrorEl = null;
+ }
+ this.sourceEl = null;
+ };
+ ElementMirror.prototype.updateElPosition = function () {
+ if (this.sourceEl && this.isVisible) {
+ applyStyle(this.getMirrorEl(), {
+ left: this.sourceElRect.left + this.deltaX,
+ top: this.sourceElRect.top + this.deltaY,
+ });
+ }
+ };
+ ElementMirror.prototype.getMirrorEl = function () {
+ var sourceElRect = this.sourceElRect;
+ var mirrorEl = this.mirrorEl;
+ if (!mirrorEl) {
+ mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true
+ // we don't want long taps or any mouse interaction causing selection/menus.
+ // would use preventSelection(), but that prevents selectstart, causing problems.
+ mirrorEl.classList.add('fc-unselectable');
+ mirrorEl.classList.add('fc-event-dragging');
+ applyStyle(mirrorEl, {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ visibility: '',
+ boxSizing: 'border-box',
+ width: sourceElRect.right - sourceElRect.left,
+ height: sourceElRect.bottom - sourceElRect.top,
+ right: 'auto',
+ bottom: 'auto',
+ margin: 0,
+ });
+ this.parentNode.appendChild(mirrorEl);
+ }
+ return mirrorEl;
+ };
+ return ElementMirror;
+ }());
+
+ /*
+ Is a cache for a given element's scroll information (all the info that ScrollController stores)
+ in addition the "client rectangle" of the element.. the area within the scrollbars.
+
+ The cache can be in one of two modes:
+ - doesListening:false - ignores when the container is scrolled by someone else
+ - doesListening:true - watch for scrolling and update the cache
+ */
+ var ScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ScrollGeomCache, _super);
+ function ScrollGeomCache(scrollController, doesListening) {
+ var _this = _super.call(this) || this;
+ _this.handleScroll = function () {
+ _this.scrollTop = _this.scrollController.getScrollTop();
+ _this.scrollLeft = _this.scrollController.getScrollLeft();
+ _this.handleScrollChange();
+ };
+ _this.scrollController = scrollController;
+ _this.doesListening = doesListening;
+ _this.scrollTop = _this.origScrollTop = scrollController.getScrollTop();
+ _this.scrollLeft = _this.origScrollLeft = scrollController.getScrollLeft();
+ _this.scrollWidth = scrollController.getScrollWidth();
+ _this.scrollHeight = scrollController.getScrollHeight();
+ _this.clientWidth = scrollController.getClientWidth();
+ _this.clientHeight = scrollController.getClientHeight();
+ _this.clientRect = _this.computeClientRect(); // do last in case it needs cached values
+ if (_this.doesListening) {
+ _this.getEventTarget().addEventListener('scroll', _this.handleScroll);
+ }
+ return _this;
+ }
+ ScrollGeomCache.prototype.destroy = function () {
+ if (this.doesListening) {
+ this.getEventTarget().removeEventListener('scroll', this.handleScroll);
+ }
+ };
+ ScrollGeomCache.prototype.getScrollTop = function () {
+ return this.scrollTop;
+ };
+ ScrollGeomCache.prototype.getScrollLeft = function () {
+ return this.scrollLeft;
+ };
+ ScrollGeomCache.prototype.setScrollTop = function (top) {
+ this.scrollController.setScrollTop(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.setScrollLeft = function (top) {
+ this.scrollController.setScrollLeft(top);
+ if (!this.doesListening) {
+ // we are not relying on the element to normalize out-of-bounds scroll values
+ // so we need to sanitize ourselves
+ this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0);
+ this.handleScrollChange();
+ }
+ };
+ ScrollGeomCache.prototype.getClientWidth = function () {
+ return this.clientWidth;
+ };
+ ScrollGeomCache.prototype.getClientHeight = function () {
+ return this.clientHeight;
+ };
+ ScrollGeomCache.prototype.getScrollWidth = function () {
+ return this.scrollWidth;
+ };
+ ScrollGeomCache.prototype.getScrollHeight = function () {
+ return this.scrollHeight;
+ };
+ ScrollGeomCache.prototype.handleScrollChange = function () {
+ };
+ return ScrollGeomCache;
+ }(ScrollController));
+
+ var ElementScrollGeomCache = /** @class */ (function (_super) {
+ __extends(ElementScrollGeomCache, _super);
+ function ElementScrollGeomCache(el, doesListening) {
+ return _super.call(this, new ElementScrollController(el), doesListening) || this;
+ }
+ ElementScrollGeomCache.prototype.getEventTarget = function () {
+ return this.scrollController.el;
+ };
+ ElementScrollGeomCache.prototype.computeClientRect = function () {
+ return computeInnerRect(this.scrollController.el);
+ };
+ return ElementScrollGeomCache;
+ }(ScrollGeomCache));
+
+ var WindowScrollGeomCache = /** @class */ (function (_super) {
+ __extends(WindowScrollGeomCache, _super);
+ function WindowScrollGeomCache(doesListening) {
+ return _super.call(this, new WindowScrollController(), doesListening) || this;
+ }
+ WindowScrollGeomCache.prototype.getEventTarget = function () {
+ return window;
+ };
+ WindowScrollGeomCache.prototype.computeClientRect = function () {
+ return {
+ left: this.scrollLeft,
+ right: this.scrollLeft + this.clientWidth,
+ top: this.scrollTop,
+ bottom: this.scrollTop + this.clientHeight,
+ };
+ };
+ // the window is the only scroll object that changes it's rectangle relative
+ // to the document's topleft as it scrolls
+ WindowScrollGeomCache.prototype.handleScrollChange = function () {
+ this.clientRect = this.computeClientRect();
+ };
+ return WindowScrollGeomCache;
+ }(ScrollGeomCache));
+
+ // If available we are using native "performance" API instead of "Date"
+ // Read more about it on MDN:
+ // https://developer.mozilla.org/en-US/docs/Web/API/Performance
+ var getTime = typeof performance === 'function' ? performance.now : Date.now;
+ /*
+ For a pointer interaction, automatically scrolls certain scroll containers when the pointer
+ approaches the edge.
+
+ The caller must call start + handleMove + stop.
+ */
+ var AutoScroller = /** @class */ (function () {
+ function AutoScroller() {
+ var _this = this;
+ // options that can be set by caller
+ this.isEnabled = true;
+ this.scrollQuery = [window, '.fc-scroller'];
+ this.edgeThreshold = 50; // pixels
+ this.maxVelocity = 300; // pixels per second
+ // internal state
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.isAnimating = false;
+ this.scrollCaches = null;
+ // protect against the initial pointerdown being too close to an edge and starting the scroll
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.animate = function () {
+ if (_this.isAnimating) { // wasn't cancelled between animation calls
+ var edge = _this.computeBestEdge(_this.pointerScreenX + window.pageXOffset, _this.pointerScreenY + window.pageYOffset);
+ if (edge) {
+ var now = getTime();
+ _this.handleSide(edge, (now - _this.msSinceRequest) / 1000);
+ _this.requestAnimation(now);
+ }
+ else {
+ _this.isAnimating = false; // will stop animation
+ }
+ }
+ };
+ }
+ AutoScroller.prototype.start = function (pageX, pageY, scrollStartEl) {
+ if (this.isEnabled) {
+ this.scrollCaches = this.buildCaches(scrollStartEl);
+ this.pointerScreenX = null;
+ this.pointerScreenY = null;
+ this.everMovedUp = false;
+ this.everMovedDown = false;
+ this.everMovedLeft = false;
+ this.everMovedRight = false;
+ this.handleMove(pageX, pageY);
+ }
+ };
+ AutoScroller.prototype.handleMove = function (pageX, pageY) {
+ if (this.isEnabled) {
+ var pointerScreenX = pageX - window.pageXOffset;
+ var pointerScreenY = pageY - window.pageYOffset;
+ var yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY;
+ var xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX;
+ if (yDelta < 0) {
+ this.everMovedUp = true;
+ }
+ else if (yDelta > 0) {
+ this.everMovedDown = true;
+ }
+ if (xDelta < 0) {
+ this.everMovedLeft = true;
+ }
+ else if (xDelta > 0) {
+ this.everMovedRight = true;
+ }
+ this.pointerScreenX = pointerScreenX;
+ this.pointerScreenY = pointerScreenY;
+ if (!this.isAnimating) {
+ this.isAnimating = true;
+ this.requestAnimation(getTime());
+ }
+ }
+ };
+ AutoScroller.prototype.stop = function () {
+ if (this.isEnabled) {
+ this.isAnimating = false; // will stop animation
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ this.scrollCaches = null;
+ }
+ };
+ AutoScroller.prototype.requestAnimation = function (now) {
+ this.msSinceRequest = now;
+ requestAnimationFrame(this.animate);
+ };
+ AutoScroller.prototype.handleSide = function (edge, seconds) {
+ var scrollCache = edge.scrollCache;
+ var edgeThreshold = this.edgeThreshold;
+ var invDistance = edgeThreshold - edge.distance;
+ var velocity = // the closer to the edge, the faster we scroll
+ ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic
+ this.maxVelocity * seconds;
+ var sign = 1;
+ switch (edge.name) {
+ case 'left':
+ sign = -1;
+ // falls through
+ case 'right':
+ scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign);
+ break;
+ case 'top':
+ sign = -1;
+ // falls through
+ case 'bottom':
+ scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign);
+ break;
+ }
+ };
+ // left/top are relative to document topleft
+ AutoScroller.prototype.computeBestEdge = function (left, top) {
+ var edgeThreshold = this.edgeThreshold;
+ var bestSide = null;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ var rect = scrollCache.clientRect;
+ var leftDist = left - rect.left;
+ var rightDist = rect.right - left;
+ var topDist = top - rect.top;
+ var bottomDist = rect.bottom - top;
+ // completely within the rect?
+ if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
+ if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() &&
+ (!bestSide || bestSide.distance > topDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist };
+ }
+ if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() &&
+ (!bestSide || bestSide.distance > bottomDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist };
+ }
+ if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() &&
+ (!bestSide || bestSide.distance > leftDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist };
+ }
+ if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() &&
+ (!bestSide || bestSide.distance > rightDist)) {
+ bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist };
+ }
+ }
+ }
+ return bestSide;
+ };
+ AutoScroller.prototype.buildCaches = function (scrollStartEl) {
+ return this.queryScrollEls(scrollStartEl).map(function (el) {
+ if (el === window) {
+ return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls
+ }
+ return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls
+ });
+ };
+ AutoScroller.prototype.queryScrollEls = function (scrollStartEl) {
+ var els = [];
+ for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) {
+ var query = _a[_i];
+ if (typeof query === 'object') {
+ els.push(query);
+ }
+ else {
+ els.push.apply(els, Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query)));
+ }
+ }
+ return els;
+ };
+ return AutoScroller;
+ }());
+
+ /*
+ Monitors dragging on an element. Has a number of high-level features:
+ - minimum distance required before dragging
+ - minimum wait time ("delay") before dragging
+ - a mirror element that follows the pointer
+ */
+ var FeaturefulElementDragging = /** @class */ (function (_super) {
+ __extends(FeaturefulElementDragging, _super);
+ function FeaturefulElementDragging(containerEl, selector) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.containerEl = containerEl;
+ // options that can be directly set by caller
+ // the caller can also set the PointerDragging's options as well
+ _this.delay = null;
+ _this.minDistance = 0;
+ _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag
+ _this.mirrorNeedsRevert = false;
+ _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup
+ _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ _this.delayTimeoutId = null;
+ _this.onPointerDown = function (ev) {
+ if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going
+ _this.isInteracting = true;
+ _this.isDelayEnded = false;
+ _this.isDistanceSurpassed = false;
+ preventSelection(document.body);
+ preventContextMenu(document.body);
+ // prevent links from being visited if there's an eventual drag.
+ // also prevents selection in older browsers (maybe?).
+ // not necessary for touch, besides, browser would complain about passiveness.
+ if (!ev.isTouch) {
+ ev.origEvent.preventDefault();
+ }
+ _this.emitter.trigger('pointerdown', ev);
+ if (_this.isInteracting && // not destroyed via pointerdown handler
+ !_this.pointer.shouldIgnoreMove) {
+ // actions related to initiating dragstart+dragmove+dragend...
+ _this.mirror.setIsVisible(false); // reset. caller must set-visible
+ _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down
+ _this.startDelay(ev);
+ if (!_this.minDistance) {
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ }
+ };
+ _this.onPointerMove = function (ev) {
+ if (_this.isInteracting) {
+ _this.emitter.trigger('pointermove', ev);
+ if (!_this.isDistanceSurpassed) {
+ var minDistance = _this.minDistance;
+ var distanceSq = void 0; // current distance from the origin, squared
+ var deltaX = ev.deltaX, deltaY = ev.deltaY;
+ distanceSq = deltaX * deltaX + deltaY * deltaY;
+ if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+ _this.handleDistanceSurpassed(ev);
+ }
+ }
+ if (_this.isDragging) {
+ // a real pointer move? (not one simulated by scrolling)
+ if (ev.origEvent.type !== 'scroll') {
+ _this.mirror.handleMove(ev.pageX, ev.pageY);
+ _this.autoScroller.handleMove(ev.pageX, ev.pageY);
+ }
+ _this.emitter.trigger('dragmove', ev);
+ }
+ }
+ };
+ _this.onPointerUp = function (ev) {
+ if (_this.isInteracting) {
+ _this.isInteracting = false;
+ allowSelection(document.body);
+ allowContextMenu(document.body);
+ _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert
+ if (_this.isDragging) {
+ _this.autoScroller.stop();
+ _this.tryStopDrag(ev); // which will stop the mirror
+ }
+ if (_this.delayTimeoutId) {
+ clearTimeout(_this.delayTimeoutId);
+ _this.delayTimeoutId = null;
+ }
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.onPointerDown);
+ pointer.emitter.on('pointermove', _this.onPointerMove);
+ pointer.emitter.on('pointerup', _this.onPointerUp);
+ if (selector) {
+ pointer.selector = selector;
+ }
+ _this.mirror = new ElementMirror();
+ _this.autoScroller = new AutoScroller();
+ return _this;
+ }
+ FeaturefulElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ // HACK: simulate a pointer-up to end the current drag
+ // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire)
+ this.onPointerUp({});
+ };
+ FeaturefulElementDragging.prototype.startDelay = function (ev) {
+ var _this = this;
+ if (typeof this.delay === 'number') {
+ this.delayTimeoutId = setTimeout(function () {
+ _this.delayTimeoutId = null;
+ _this.handleDelayEnd(ev);
+ }, this.delay); // not assignable to number!
+ }
+ else {
+ this.handleDelayEnd(ev);
+ }
+ };
+ FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) {
+ this.isDelayEnded = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) {
+ this.isDistanceSurpassed = true;
+ this.tryStartDrag(ev);
+ };
+ FeaturefulElementDragging.prototype.tryStartDrag = function (ev) {
+ if (this.isDelayEnded && this.isDistanceSurpassed) {
+ if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) {
+ this.isDragging = true;
+ this.mirrorNeedsRevert = false;
+ this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl);
+ this.emitter.trigger('dragstart', ev);
+ if (this.touchScrollAllowed === false) {
+ this.pointer.cancelTouchScroll();
+ }
+ }
+ }
+ };
+ FeaturefulElementDragging.prototype.tryStopDrag = function (ev) {
+ // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events
+ // that come from the document to fire beforehand. much more convenient this way.
+ this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev));
+ };
+ FeaturefulElementDragging.prototype.stopDrag = function (ev) {
+ this.isDragging = false;
+ this.emitter.trigger('dragend', ev);
+ };
+ // fill in the implementations...
+ FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.pointer.shouldIgnoreMove = bool;
+ };
+ FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ this.mirror.setIsVisible(bool);
+ };
+ FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) {
+ this.mirrorNeedsRevert = bool;
+ };
+ FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) {
+ this.autoScroller.isEnabled = bool;
+ };
+ return FeaturefulElementDragging;
+ }(ElementDragging));
+
+ /*
+ When this class is instantiated, it records the offset of an element (relative to the document topleft),
+ and continues to monitor scrolling, updating the cached coordinates if it needs to.
+ Does not access the DOM after instantiation, so highly performant.
+
+ Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element
+ and an determine if a given point is inside the combined clipping rectangle.
+ */
+ var OffsetTracker = /** @class */ (function () {
+ function OffsetTracker(el) {
+ this.origRect = computeRect(el);
+ // will work fine for divs that have overflow:hidden
+ this.scrollCaches = getClippingParents(el).map(function (scrollEl) { return new ElementScrollGeomCache(scrollEl, true); });
+ }
+ OffsetTracker.prototype.destroy = function () {
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ scrollCache.destroy();
+ }
+ };
+ OffsetTracker.prototype.computeLeft = function () {
+ var left = this.origRect.left;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ left += scrollCache.origScrollLeft - scrollCache.getScrollLeft();
+ }
+ return left;
+ };
+ OffsetTracker.prototype.computeTop = function () {
+ var top = this.origRect.top;
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ top += scrollCache.origScrollTop - scrollCache.getScrollTop();
+ }
+ return top;
+ };
+ OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) {
+ var point = { left: pageX, top: pageY };
+ for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) {
+ var scrollCache = _a[_i];
+ if (!isIgnoredClipping(scrollCache.getEventTarget()) &&
+ !pointInsideRect(point, scrollCache.clientRect)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ return OffsetTracker;
+ }());
+ // certain clipping containers should never constrain interactions, like and
+ // https://github.com/fullcalendar/fullcalendar/issues/3615
+ function isIgnoredClipping(node) {
+ var tagName = node.tagName;
+ return tagName === 'HTML' || tagName === 'BODY';
+ }
+
+ /*
+ Tracks movement over multiple droppable areas (aka "hits")
+ that exist in one or more DateComponents.
+ Relies on an existing draggable.
+
+ emits:
+ - pointerdown
+ - dragstart
+ - hitchange - fires initially, even if not over a hit
+ - pointerup
+ - (hitchange - again, to null, if ended over a hit)
+ - dragend
+ */
+ var HitDragging = /** @class */ (function () {
+ function HitDragging(dragging, droppableStore) {
+ var _this = this;
+ // options that can be set by caller
+ this.useSubjectCenter = false;
+ this.requireInitial = true; // if doesn't start out on a hit, won't emit any events
+ this.initialHit = null;
+ this.movingHit = null;
+ this.finalHit = null; // won't ever be populated if shouldIgnoreMove
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ _this.initialHit = null;
+ _this.movingHit = null;
+ _this.finalHit = null;
+ _this.prepareHits();
+ _this.processFirstCoord(ev);
+ if (_this.initialHit || !_this.requireInitial) {
+ dragging.setIgnoreMove(false);
+ // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :(
+ _this.emitter.trigger('pointerdown', ev);
+ }
+ else {
+ dragging.setIgnoreMove(true);
+ }
+ };
+ this.handleDragStart = function (ev) {
+ _this.emitter.trigger('dragstart', ev);
+ _this.handleMove(ev, true); // force = fire even if initially null
+ };
+ this.handleDragMove = function (ev) {
+ _this.emitter.trigger('dragmove', ev);
+ _this.handleMove(ev);
+ };
+ this.handlePointerUp = function (ev) {
+ _this.releaseHits();
+ _this.emitter.trigger('pointerup', ev);
+ };
+ this.handleDragEnd = function (ev) {
+ if (_this.movingHit) {
+ _this.emitter.trigger('hitupdate', null, true, ev);
+ }
+ _this.finalHit = _this.movingHit;
+ _this.movingHit = null;
+ _this.emitter.trigger('dragend', ev);
+ };
+ this.droppableStore = droppableStore;
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ dragging.emitter.on('dragmove', this.handleDragMove);
+ dragging.emitter.on('pointerup', this.handlePointerUp);
+ dragging.emitter.on('dragend', this.handleDragEnd);
+ this.dragging = dragging;
+ this.emitter = new Emitter();
+ }
+ // sets initialHit
+ // sets coordAdjust
+ HitDragging.prototype.processFirstCoord = function (ev) {
+ var origPoint = { left: ev.pageX, top: ev.pageY };
+ var adjustedPoint = origPoint;
+ var subjectEl = ev.subjectEl;
+ var subjectRect;
+ if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot
+ subjectRect = computeRect(subjectEl);
+ adjustedPoint = constrainPoint(adjustedPoint, subjectRect);
+ }
+ var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top);
+ if (initialHit) {
+ if (this.useSubjectCenter && subjectRect) {
+ var slicedSubjectRect = intersectRects(subjectRect, initialHit.rect);
+ if (slicedSubjectRect) {
+ adjustedPoint = getRectCenter(slicedSubjectRect);
+ }
+ }
+ this.coordAdjust = diffPoints(adjustedPoint, origPoint);
+ }
+ else {
+ this.coordAdjust = { left: 0, top: 0 };
+ }
+ };
+ HitDragging.prototype.handleMove = function (ev, forceHandle) {
+ var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top);
+ if (forceHandle || !isHitsEqual(this.movingHit, hit)) {
+ this.movingHit = hit;
+ this.emitter.trigger('hitupdate', hit, false, ev);
+ }
+ };
+ HitDragging.prototype.prepareHits = function () {
+ this.offsetTrackers = mapHash(this.droppableStore, function (interactionSettings) {
+ interactionSettings.component.prepareHits();
+ return new OffsetTracker(interactionSettings.el);
+ });
+ };
+ HitDragging.prototype.releaseHits = function () {
+ var offsetTrackers = this.offsetTrackers;
+ for (var id in offsetTrackers) {
+ offsetTrackers[id].destroy();
+ }
+ this.offsetTrackers = {};
+ };
+ HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) {
+ var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers;
+ var bestHit = null;
+ for (var id in droppableStore) {
+ var component = droppableStore[id].component;
+ var offsetTracker = offsetTrackers[id];
+ if (offsetTracker && // wasn't destroyed mid-drag
+ offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
+ var originLeft = offsetTracker.computeLeft();
+ var originTop = offsetTracker.computeTop();
+ var positionLeft = offsetLeft - originLeft;
+ var positionTop = offsetTop - originTop;
+ var origRect = offsetTracker.origRect;
+ var width = origRect.right - origRect.left;
+ var height = origRect.bottom - origRect.top;
+ if (
+ // must be within the element's bounds
+ positionLeft >= 0 && positionLeft < width &&
+ positionTop >= 0 && positionTop < height) {
+ var hit = component.queryHit(positionLeft, positionTop, width, height);
+ if (hit && (
+ // make sure the hit is within activeRange, meaning it's not a dead cell
+ rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) &&
+ (!bestHit || hit.layer > bestHit.layer)) {
+ hit.componentId = id;
+ hit.context = component.context;
+ // TODO: better way to re-orient rectangle
+ hit.rect.left += originLeft;
+ hit.rect.right += originLeft;
+ hit.rect.top += originTop;
+ hit.rect.bottom += originTop;
+ bestHit = hit;
+ }
+ }
+ }
+ }
+ return bestHit;
+ };
+ return HitDragging;
+ }());
+ function isHitsEqual(hit0, hit1) {
+ if (!hit0 && !hit1) {
+ return true;
+ }
+ if (Boolean(hit0) !== Boolean(hit1)) {
+ return false;
+ }
+ return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan);
+ }
+
+ function buildDatePointApiWithContext(dateSpan, context) {
+ var props = {};
+ for (var _i = 0, _a = context.pluginHooks.datePointTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(props, transform(dateSpan, context));
+ }
+ __assign(props, buildDatePointApi(dateSpan, context.dateEnv));
+ return props;
+ }
+ function buildDatePointApi(span, dateEnv) {
+ return {
+ date: dateEnv.toDate(span.range.start),
+ dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }),
+ allDay: span.allDay,
+ };
+ }
+
+ /*
+ Monitors when the user clicks on a specific date/time of a component.
+ A pointerdown+pointerup on the same "hit" constitutes a click.
+ */
+ var DateClicking = /** @class */ (function (_super) {
+ __extends(DateClicking, _super);
+ function DateClicking(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.handlePointerDown = function (pev) {
+ var dragging = _this.dragging;
+ var downEl = pev.origEvent.target;
+ // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
+ dragging.setIgnoreMove(!_this.component.isValidDateDownEl(downEl));
+ };
+ // won't even fire if moving was ignored
+ _this.handleDragEnd = function (ev) {
+ var component = _this.component;
+ var pointer = _this.dragging.pointer;
+ if (!pointer.wasTouchScroll) {
+ var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit;
+ if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) {
+ var context = component.context;
+ var arg = __assign(__assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view });
+ context.emitter.trigger('dateClick', arg);
+ }
+ }
+ };
+ // we DO want to watch pointer moves because otherwise finalHit won't get populated
+ _this.dragging = new FeaturefulElementDragging(settings.el);
+ _this.dragging.autoScroller.isEnabled = false;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ DateClicking.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateClicking;
+ }(Interaction));
+
+ /*
+ Tracks when the user selects a portion of time of a component,
+ constituted by a drag over date cells, with a possible delay at the beginning of the drag.
+ */
+ var DateSelecting = /** @class */ (function (_super) {
+ __extends(DateSelecting, _super);
+ function DateSelecting(settings) {
+ var _this = _super.call(this, settings) || this;
+ _this.dragSelection = null;
+ _this.handlePointerDown = function (ev) {
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var options = component.context.options;
+ var canSelect = options.selectable &&
+ component.isValidDateDownEl(ev.origEvent.target);
+ // don't bother to watch expensive moves if component won't do selection
+ dragging.setIgnoreMove(!canSelect);
+ // if touch, require user to hold down
+ dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null;
+ };
+ _this.handleDragStart = function (ev) {
+ _this.component.context.calendarApi.unselect(ev); // unselect previous selections
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ var context = _this.component.context;
+ var dragSelection = null;
+ var isInvalid = false;
+ if (hit) {
+ var initialHit = _this.hitDragging.initialHit;
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers);
+ }
+ if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
+ isInvalid = true;
+ dragSelection = null;
+ }
+ }
+ if (dragSelection) {
+ context.dispatch({ type: 'SELECT_DATES', selection: dragSelection });
+ }
+ else if (!isFinal) { // only unselect if moved away while dragging
+ context.dispatch({ type: 'UNSELECT_DATES' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging
+ }
+ };
+ _this.handlePointerUp = function (pev) {
+ if (_this.dragSelection) {
+ // selection is already rendered, so just need to report selection
+ triggerDateSelect(_this.dragSelection, pev, _this.component.context);
+ _this.dragSelection = null;
+ }
+ };
+ var component = settings.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.touchScrollAllowed = false;
+ dragging.minDistance = options.selectMinDistance || 0;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ DateSelecting.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return DateSelecting;
+ }(Interaction));
+ function getComponentTouchDelay$1(component) {
+ var options = component.context.options;
+ var delay = options.selectLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+ function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var ms = [
+ dateSpan0.range.start,
+ dateSpan0.range.end,
+ dateSpan1.range.start,
+ dateSpan1.range.end,
+ ];
+ ms.sort(compareNumbers);
+ var props = {};
+ for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) {
+ var transformer = dateSelectionTransformers_1[_i];
+ var res = transformer(hit0, hit1);
+ if (res === false) {
+ return null;
+ }
+ if (res) {
+ __assign(props, res);
+ }
+ }
+ props.range = { start: ms[0], end: ms[3] };
+ props.allDay = dateSpan0.allDay;
+ return props;
+ }
+
+ var EventDragging = /** @class */ (function (_super) {
+ __extends(EventDragging, _super);
+ function EventDragging(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.subjectEl = null;
+ _this.subjectSeg = null; // the seg being selected/dragged
+ _this.isDragging = false;
+ _this.eventRange = null;
+ _this.relevantEvents = null; // the events being dragged
+ _this.receivingContext = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var origTarget = ev.origEvent.target;
+ var _a = _this, component = _a.component, dragging = _a.dragging;
+ var mirror = dragging.mirror;
+ var options = component.context.options;
+ var initialContext = component.context;
+ _this.subjectEl = ev.subjectEl;
+ var subjectSeg = _this.subjectSeg = getElSeg(ev.subjectEl);
+ var eventRange = _this.eventRange = subjectSeg.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ _this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId);
+ dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance;
+ dragging.delay =
+ // only do a touch delay if touch and this event hasn't been selected yet
+ (ev.isTouch && eventInstanceId !== component.props.eventSelection) ?
+ getComponentTouchDelay(component) :
+ null;
+ if (options.fixedMirrorParent) {
+ mirror.parentNode = options.fixedMirrorParent;
+ }
+ else {
+ mirror.parentNode = elementClosest(origTarget, '.fc');
+ }
+ mirror.revertDuration = options.dragRevertDuration;
+ var isValid = component.isValidSegDownEl(origTarget) &&
+ !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer
+ dragging.setIgnoreMove(!isValid);
+ // disable dragging for elements that are resizable (ie, selectable)
+ // but are not draggable
+ _this.isDragging = isValid &&
+ ev.subjectEl.classList.contains('fc-event-draggable');
+ };
+ _this.handleDragStart = function (ev) {
+ var initialContext = _this.component.context;
+ var eventRange = _this.eventRange;
+ var eventInstanceId = eventRange.instance.instanceId;
+ if (ev.isTouch) {
+ // need to select a different event?
+ if (eventInstanceId !== _this.component.props.eventSelection) {
+ initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId });
+ }
+ }
+ else {
+ // if now using mouse, but was previous touch interaction, clear selected event
+ initialContext.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ if (_this.isDragging) {
+ initialContext.calendarApi.unselect(ev); // unselect *date* selection
+ initialContext.emitter.trigger('eventDragStart', {
+ el: _this.subjectEl,
+ event: new EventApi(initialContext, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: initialContext.viewApi,
+ });
+ }
+ };
+ _this.handleHitUpdate = function (hit, isFinal) {
+ if (!_this.isDragging) {
+ return;
+ }
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var initialContext = _this.component.context;
+ // states based on new hit
+ var receivingContext = null;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ var receivingOptions = receivingContext.options;
+ if (initialContext === receivingContext ||
+ (receivingOptions.editable && receivingOptions.droppable)) {
+ mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers);
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = createEmptyEventStore();
+ }
+ }
+ }
+ else {
+ receivingContext = null;
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (initialContext === receivingContext && // TODO: write test for this
+ isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.dragging.setMirrorNeedsRevert(!mutation);
+ // render the mirror if no already-rendered mirror
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ _this.dragging.setMirrorIsVisible(!hit || !getElRoot(_this.subjectEl).querySelector('.fc-event-mirror'));
+ // assign states based on new hit
+ _this.receivingContext = receivingContext;
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handlePointerUp = function () {
+ if (!_this.isDragging) {
+ _this.cleanup(); // because handleDragEnd won't fire
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ if (_this.isDragging) {
+ var initialContext_1 = _this.component.context;
+ var initialView = initialContext_1.viewApi;
+ var _a = _this, receivingContext_1 = _a.receivingContext, validMutation = _a.validMutation;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(initialContext_1, eventDef, eventInstance);
+ var relevantEvents_1 = _this.relevantEvents;
+ var mutatedRelevantEvents_1 = _this.mutatedRelevantEvents;
+ var finalHit = _this.hitDragging.finalHit;
+ _this.clearDrag(); // must happen after revert animation
+ initialContext_1.emitter.trigger('eventDragStop', {
+ el: _this.subjectEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: initialView,
+ });
+ if (validMutation) {
+ // dropped within same calendar
+ if (receivingContext_1 === initialContext_1) {
+ var updatedEventApi = new EventApi(initialContext_1, mutatedRelevantEvents_1.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents_1.instances[eventInstance.instanceId] : null);
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1, // the pre-change data
+ });
+ },
+ };
+ var transformed = {};
+ for (var _i = 0, _b = initialContext_1.getCurrentData().pluginHooks.eventDropTransformers; _i < _b.length; _i++) {
+ var transformer = _b[_i];
+ __assign(transformed, transformer(validMutation, initialContext_1));
+ }
+ initialContext_1.emitter.trigger('eventDrop', __assign(__assign(__assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView }));
+ initialContext_1.emitter.trigger('eventChange', eventChangeArg);
+ // dropped in different calendar
+ }
+ else if (receivingContext_1) {
+ var eventRemoveArg = {
+ event: eventApi,
+ relatedEvents: buildEventApis(relevantEvents_1, initialContext_1, eventInstance),
+ revert: function () {
+ initialContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ },
+ };
+ initialContext_1.emitter.trigger('eventLeave', __assign(__assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView }));
+ initialContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: relevantEvents_1,
+ });
+ initialContext_1.emitter.trigger('eventRemove', eventRemoveArg);
+ var addedEventDef = mutatedRelevantEvents_1.defs[eventDef.defId];
+ var addedEventInstance = mutatedRelevantEvents_1.instances[eventInstance.instanceId];
+ var addedEventApi = new EventApi(receivingContext_1, addedEventDef, addedEventInstance);
+ receivingContext_1.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ var eventAddArg = {
+ event: addedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents_1, receivingContext_1, addedEventInstance),
+ revert: function () {
+ receivingContext_1.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: mutatedRelevantEvents_1,
+ });
+ },
+ };
+ receivingContext_1.emitter.trigger('eventAdd', eventAddArg);
+ if (ev.isTouch) {
+ receivingContext_1.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: eventInstance.instanceId,
+ });
+ }
+ receivingContext_1.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext_1)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi }));
+ receivingContext_1.emitter.trigger('eventReceive', __assign(__assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi }));
+ }
+ }
+ else {
+ initialContext_1.emitter.trigger('_noEventDrop');
+ }
+ }
+ _this.cleanup();
+ };
+ var component = _this.component;
+ var options = component.context.options;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = EventDragging.SELECTOR;
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsStore);
+ hitDragging.useSubjectCenter = settings.useEventCenter;
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('pointerup', _this.handlePointerUp);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventDragging.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ // render a drag state on the next receivingCalendar
+ EventDragging.prototype.displayDrag = function (nextContext, state) {
+ var initialContext = this.component.context;
+ var prevContext = this.receivingContext;
+ // does the previous calendar need to be cleared?
+ if (prevContext && prevContext !== nextContext) {
+ // does the initial calendar need to be cleared?
+ // if so, don't clear all the way. we still need to to hide the affectedEvents
+ if (prevContext === initialContext) {
+ prevContext.dispatch({
+ type: 'SET_EVENT_DRAG',
+ state: {
+ affectedEvents: state.affectedEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ },
+ });
+ // completely clear the old calendar if it wasn't the initial
+ }
+ else {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ EventDragging.prototype.clearDrag = function () {
+ var initialCalendar = this.component.context;
+ var receivingContext = this.receivingContext;
+ if (receivingContext) {
+ receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ // the initial calendar might have an dummy drag state from displayDrag
+ if (initialCalendar !== receivingContext) {
+ initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ EventDragging.prototype.cleanup = function () {
+ this.subjectSeg = null;
+ this.isDragging = false;
+ this.eventRange = null;
+ this.relevantEvents = null;
+ this.receivingContext = null;
+ this.validMutation = null;
+ this.mutatedRelevantEvents = null;
+ };
+ // TODO: test this in IE11
+ // QUESTION: why do we need it on the resizable???
+ EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable';
+ return EventDragging;
+ }(Interaction));
+ function computeEventMutation(hit0, hit1, massagers) {
+ var dateSpan0 = hit0.dateSpan;
+ var dateSpan1 = hit1.dateSpan;
+ var date0 = dateSpan0.range.start;
+ var date1 = dateSpan1.range.start;
+ var standardProps = {};
+ if (dateSpan0.allDay !== dateSpan1.allDay) {
+ standardProps.allDay = dateSpan1.allDay;
+ standardProps.hasEnd = hit1.context.options.allDayMaintainDuration;
+ if (dateSpan1.allDay) {
+ // means date1 is already start-of-day,
+ // but date0 needs to be converted
+ date0 = startOfDay(date0);
+ }
+ }
+ var delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ?
+ hit0.largeUnit :
+ null);
+ if (delta.milliseconds) { // has hours/minutes/seconds
+ standardProps.allDay = false;
+ }
+ var mutation = {
+ datesDelta: delta,
+ standardProps: standardProps,
+ };
+ for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) {
+ var massager = massagers_1[_i];
+ massager(mutation, hit0, hit1);
+ }
+ return mutation;
+ }
+ function getComponentTouchDelay(component) {
+ var options = component.context.options;
+ var delay = options.eventLongPressDelay;
+ if (delay == null) {
+ delay = options.longPressDelay;
+ }
+ return delay;
+ }
+
+ var EventResizing = /** @class */ (function (_super) {
+ __extends(EventResizing, _super);
+ function EventResizing(settings) {
+ var _this = _super.call(this, settings) || this;
+ // internal state
+ _this.draggingSegEl = null;
+ _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg?
+ _this.eventRange = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ _this.mutatedRelevantEvents = null;
+ _this.handlePointerDown = function (ev) {
+ var component = _this.component;
+ var segEl = _this.querySegEl(ev);
+ var seg = getElSeg(segEl);
+ var eventRange = _this.eventRange = seg.eventRange;
+ _this.dragging.minDistance = component.context.options.eventDragMinDistance;
+ // if touch, need to be working with a selected event
+ _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) ||
+ (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId));
+ };
+ _this.handleDragStart = function (ev) {
+ var context = _this.component.context;
+ var eventRange = _this.eventRange;
+ _this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, _this.eventRange.instance.instanceId);
+ var segEl = _this.querySegEl(ev);
+ _this.draggingSegEl = segEl;
+ _this.draggingSeg = getElSeg(segEl);
+ context.calendarApi.unselect();
+ context.emitter.trigger('eventResizeStart', {
+ el: segEl,
+ event: new EventApi(context, eventRange.def, eventRange.instance),
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ };
+ _this.handleHitUpdate = function (hit, isFinal, ev) {
+ var context = _this.component.context;
+ var relevantEvents = _this.relevantEvents;
+ var initialHit = _this.hitDragging.initialHit;
+ var eventInstance = _this.eventRange.instance;
+ var mutation = null;
+ var mutatedRelevantEvents = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: relevantEvents,
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: true,
+ };
+ if (hit) {
+ var disallowed = hit.componentId === initialHit.componentId
+ && _this.isHitComboAllowed
+ && !_this.isHitComboAllowed(initialHit, hit);
+ if (!disallowed) {
+ mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range);
+ }
+ }
+ if (mutation) {
+ mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context);
+ interaction.mutatedEvents = mutatedRelevantEvents;
+ if (!isInteractionValid(interaction, hit.dateProfile, context)) {
+ isInvalid = true;
+ mutation = null;
+ mutatedRelevantEvents = null;
+ interaction.mutatedEvents = null;
+ }
+ }
+ if (mutatedRelevantEvents) {
+ context.dispatch({
+ type: 'SET_EVENT_RESIZE',
+ state: interaction,
+ });
+ }
+ else {
+ context.dispatch({ type: 'UNSET_EVENT_RESIZE' });
+ }
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ if (mutation && isHitsEqual(initialHit, hit)) {
+ mutation = null;
+ }
+ _this.validMutation = mutation;
+ _this.mutatedRelevantEvents = mutatedRelevantEvents;
+ }
+ };
+ _this.handleDragEnd = function (ev) {
+ var context = _this.component.context;
+ var eventDef = _this.eventRange.def;
+ var eventInstance = _this.eventRange.instance;
+ var eventApi = new EventApi(context, eventDef, eventInstance);
+ var relevantEvents = _this.relevantEvents;
+ var mutatedRelevantEvents = _this.mutatedRelevantEvents;
+ context.emitter.trigger('eventResizeStop', {
+ el: _this.draggingSegEl,
+ event: eventApi,
+ jsEvent: ev.origEvent,
+ view: context.viewApi,
+ });
+ if (_this.validMutation) {
+ var updatedEventApi = new EventApi(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: mutatedRelevantEvents,
+ });
+ var eventChangeArg = {
+ oldEvent: eventApi,
+ event: updatedEventApi,
+ relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance),
+ revert: function () {
+ context.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: relevantEvents, // the pre-change events
+ });
+ },
+ };
+ context.emitter.trigger('eventResize', __assign(__assign({}, eventChangeArg), { el: _this.draggingSegEl, startDelta: _this.validMutation.startDelta || createDuration(0), endDelta: _this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi }));
+ context.emitter.trigger('eventChange', eventChangeArg);
+ }
+ else {
+ context.emitter.trigger('_noEventResize');
+ }
+ // reset all internal state
+ _this.draggingSeg = null;
+ _this.relevantEvents = null;
+ _this.validMutation = null;
+ // okay to keep eventInstance around. useful to set it in handlePointerDown
+ };
+ var component = settings.component;
+ var dragging = _this.dragging = new FeaturefulElementDragging(settings.el);
+ dragging.pointer.selector = '.fc-event-resizer';
+ dragging.touchScrollAllowed = false;
+ dragging.autoScroller.isEnabled = component.context.options.dragScroll;
+ var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings));
+ hitDragging.emitter.on('pointerdown', _this.handlePointerDown);
+ hitDragging.emitter.on('dragstart', _this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', _this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', _this.handleDragEnd);
+ return _this;
+ }
+ EventResizing.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ EventResizing.prototype.querySegEl = function (ev) {
+ return elementClosest(ev.subjectEl, '.fc-event');
+ };
+ return EventResizing;
+ }(Interaction));
+ function computeMutation(hit0, hit1, isFromStart, instanceRange) {
+ var dateEnv = hit0.context.dateEnv;
+ var date0 = hit0.dateSpan.range.start;
+ var date1 = hit1.dateSpan.range.start;
+ var delta = diffDates(date0, date1, dateEnv, hit0.largeUnit);
+ if (isFromStart) {
+ if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
+ return { startDelta: delta };
+ }
+ }
+ else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
+ return { endDelta: delta };
+ }
+ return null;
+ }
+
+ var UnselectAuto = /** @class */ (function () {
+ function UnselectAuto(context) {
+ var _this = this;
+ this.context = context;
+ this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system
+ this.matchesCancel = false;
+ this.matchesEvent = false;
+ this.onSelect = function (selectInfo) {
+ if (selectInfo.jsEvent) {
+ _this.isRecentPointerDateSelect = true;
+ }
+ };
+ this.onDocumentPointerDown = function (pev) {
+ var unselectCancel = _this.context.options.unselectCancel;
+ var downEl = getEventTargetViaRoot(pev.origEvent);
+ _this.matchesCancel = !!elementClosest(downEl, unselectCancel);
+ _this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event?
+ };
+ this.onDocumentPointerUp = function (pev) {
+ var context = _this.context;
+ var documentPointer = _this.documentPointer;
+ var calendarState = context.getCurrentData();
+ // touch-scrolling should never unfocus any type of selection
+ if (!documentPointer.wasTouchScroll) {
+ if (calendarState.dateSelection && // an existing date selection?
+ !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
+ ) {
+ var unselectAuto = context.options.unselectAuto;
+ if (unselectAuto && (!unselectAuto || !_this.matchesCancel)) {
+ context.calendarApi.unselect(pev);
+ }
+ }
+ if (calendarState.eventSelection && // an existing event selected?
+ !_this.matchesEvent // interaction DIDN'T start on an event
+ ) {
+ context.dispatch({ type: 'UNSELECT_EVENT' });
+ }
+ }
+ _this.isRecentPointerDateSelect = false;
+ };
+ var documentPointer = this.documentPointer = new PointerDragging(document);
+ documentPointer.shouldIgnoreMove = true;
+ documentPointer.shouldWatchScroll = false;
+ documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown);
+ documentPointer.emitter.on('pointerup', this.onDocumentPointerUp);
+ /*
+ TODO: better way to know about whether there was a selection with the pointer
+ */
+ context.emitter.on('select', this.onSelect);
+ }
+ UnselectAuto.prototype.destroy = function () {
+ this.context.emitter.off('select', this.onSelect);
+ this.documentPointer.destroy();
+ };
+ return UnselectAuto;
+ }());
+
+ var OPTION_REFINERS$5 = {
+ fixedMirrorParent: identity,
+ };
+ var LISTENER_REFINERS$1 = {
+ dateClick: identity,
+ eventDragStart: identity,
+ eventDragStop: identity,
+ eventDrop: identity,
+ eventResizeStart: identity,
+ eventResizeStop: identity,
+ eventResize: identity,
+ drop: identity,
+ eventReceive: identity,
+ eventLeave: identity,
+ };
+
+ /*
+ Given an already instantiated draggable object for one-or-more elements,
+ Interprets any dragging as an attempt to drag an events that lives outside
+ of a calendar onto a calendar.
+ */
+ var ExternalElementDragging = /** @class */ (function () {
+ function ExternalElementDragging(dragging, suppliedDragMeta) {
+ var _this = this;
+ this.receivingContext = null;
+ this.droppableEvent = null; // will exist for all drags, even if create:false
+ this.suppliedDragMeta = null;
+ this.dragMeta = null;
+ this.handleDragStart = function (ev) {
+ _this.dragMeta = _this.buildDragMeta(ev.subjectEl);
+ };
+ this.handleHitUpdate = function (hit, isFinal, ev) {
+ var dragging = _this.hitDragging.dragging;
+ var receivingContext = null;
+ var droppableEvent = null;
+ var isInvalid = false;
+ var interaction = {
+ affectedEvents: createEmptyEventStore(),
+ mutatedEvents: createEmptyEventStore(),
+ isEvent: _this.dragMeta.create,
+ };
+ if (hit) {
+ receivingContext = hit.context;
+ if (_this.canDropElOnCalendar(ev.subjectEl, receivingContext)) {
+ droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingContext);
+ interaction.mutatedEvents = eventTupleToStore(droppableEvent);
+ isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext);
+ if (isInvalid) {
+ interaction.mutatedEvents = createEmptyEventStore();
+ droppableEvent = null;
+ }
+ }
+ }
+ _this.displayDrag(receivingContext, interaction);
+ // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
+ // TODO: wish we could somehow wait for dispatch to guarantee render
+ dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'));
+ if (!isInvalid) {
+ enableCursor();
+ }
+ else {
+ disableCursor();
+ }
+ if (!isFinal) {
+ dragging.setMirrorNeedsRevert(!droppableEvent);
+ _this.receivingContext = receivingContext;
+ _this.droppableEvent = droppableEvent;
+ }
+ };
+ this.handleDragEnd = function (pev) {
+ var _a = _this, receivingContext = _a.receivingContext, droppableEvent = _a.droppableEvent;
+ _this.clearDrag();
+ if (receivingContext && droppableEvent) {
+ var finalHit = _this.hitDragging.finalHit;
+ var finalView = finalHit.context.viewApi;
+ var dragMeta = _this.dragMeta;
+ receivingContext.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView }));
+ if (dragMeta.create) {
+ var addingEvents_1 = eventTupleToStore(droppableEvent);
+ receivingContext.dispatch({
+ type: 'MERGE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ if (pev.isTouch) {
+ receivingContext.dispatch({
+ type: 'SELECT_EVENT',
+ eventInstanceId: droppableEvent.instance.instanceId,
+ });
+ }
+ // signal that an external event landed
+ receivingContext.emitter.trigger('eventReceive', {
+ event: new EventApi(receivingContext, droppableEvent.def, droppableEvent.instance),
+ relatedEvents: [],
+ revert: function () {
+ receivingContext.dispatch({
+ type: 'REMOVE_EVENTS',
+ eventStore: addingEvents_1,
+ });
+ },
+ draggedEl: pev.subjectEl,
+ view: finalView,
+ });
+ }
+ }
+ _this.receivingContext = null;
+ _this.droppableEvent = null;
+ };
+ var hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore);
+ hitDragging.requireInitial = false; // will start outside of a component
+ hitDragging.emitter.on('dragstart', this.handleDragStart);
+ hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
+ hitDragging.emitter.on('dragend', this.handleDragEnd);
+ this.suppliedDragMeta = suppliedDragMeta;
+ }
+ ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) {
+ if (typeof this.suppliedDragMeta === 'object') {
+ return parseDragMeta(this.suppliedDragMeta);
+ }
+ if (typeof this.suppliedDragMeta === 'function') {
+ return parseDragMeta(this.suppliedDragMeta(subjectEl));
+ }
+ return getDragMetaFromEl(subjectEl);
+ };
+ ExternalElementDragging.prototype.displayDrag = function (nextContext, state) {
+ var prevContext = this.receivingContext;
+ if (prevContext && prevContext !== nextContext) {
+ prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ if (nextContext) {
+ nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state });
+ }
+ };
+ ExternalElementDragging.prototype.clearDrag = function () {
+ if (this.receivingContext) {
+ this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
+ }
+ };
+ ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingContext) {
+ var dropAccept = receivingContext.options.dropAccept;
+ if (typeof dropAccept === 'function') {
+ return dropAccept.call(receivingContext.calendarApi, el);
+ }
+ if (typeof dropAccept === 'string' && dropAccept) {
+ return Boolean(elementMatches(el, dropAccept));
+ }
+ return true;
+ };
+ return ExternalElementDragging;
+ }());
+ // Utils for computing event store from the DragMeta
+ // ----------------------------------------------------------------------------------------------------
+ function computeEventForDateSpan(dateSpan, dragMeta, context) {
+ var defProps = __assign({}, dragMeta.leftoverProps);
+ for (var _i = 0, _a = context.pluginHooks.externalDefTransforms; _i < _a.length; _i++) {
+ var transform = _a[_i];
+ __assign(defProps, transform(dateSpan, dragMeta));
+ }
+ var _b = refineEventDef(defProps, context), refined = _b.refined, extra = _b.extra;
+ var def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
+ context);
+ var start = dateSpan.range.start;
+ // only rely on time info if drop zone is all-day,
+ // otherwise, we already know the time
+ if (dateSpan.allDay && dragMeta.startTime) {
+ start = context.dateEnv.add(start, dragMeta.startTime);
+ }
+ var end = dragMeta.duration ?
+ context.dateEnv.add(start, dragMeta.duration) :
+ getDefaultEventEnd(dateSpan.allDay, start, context);
+ var instance = createEventInstance(def.defId, { start: start, end: end });
+ return { def: def, instance: instance };
+ }
+ // Utils for extracting data from element
+ // ----------------------------------------------------------------------------------------------------
+ function getDragMetaFromEl(el) {
+ var str = getEmbeddedElData(el, 'event');
+ var obj = str ?
+ JSON.parse(str) :
+ { create: false }; // if no embedded data, assume no event creation
+ return parseDragMeta(obj);
+ }
+ config.dataAttrPrefix = '';
+ function getEmbeddedElData(el, name) {
+ var prefix = config.dataAttrPrefix;
+ var prefixedName = (prefix ? prefix + '-' : '') + name;
+ return el.getAttribute('data-' + prefixedName) || '';
+ }
+
+ /*
+ Makes an element (that is *external* to any calendar) draggable.
+ Can pass in data that determines how an event will be created when dropped onto a calendar.
+ Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system.
+ */
+ var ExternalDraggable = /** @class */ (function () {
+ function ExternalDraggable(el, settings) {
+ var _this = this;
+ if (settings === void 0) { settings = {}; }
+ this.handlePointerDown = function (ev) {
+ var dragging = _this.dragging;
+ var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay;
+ dragging.minDistance =
+ minDistance != null ?
+ minDistance :
+ (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance);
+ dragging.delay =
+ ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv
+ (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) :
+ 0;
+ };
+ this.handleDragStart = function (ev) {
+ if (ev.isTouch &&
+ _this.dragging.delay &&
+ ev.subjectEl.classList.contains('fc-event')) {
+ _this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected');
+ }
+ };
+ this.settings = settings;
+ var dragging = this.dragging = new FeaturefulElementDragging(el);
+ dragging.touchScrollAllowed = false;
+ if (settings.itemSelector != null) {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ if (settings.appendTo != null) {
+ dragging.mirror.parentNode = settings.appendTo; // TODO: write tests
+ }
+ dragging.emitter.on('pointerdown', this.handlePointerDown);
+ dragging.emitter.on('dragstart', this.handleDragStart);
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ExternalDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ExternalDraggable;
+ }());
+
+ /*
+ Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements.
+ The third-party system is responsible for drawing the visuals effects of the drag.
+ This class simply monitors for pointer movements and fires events.
+ It also has the ability to hide the moving element (the "mirror") during the drag.
+ */
+ var InferredElementDragging = /** @class */ (function (_super) {
+ __extends(InferredElementDragging, _super);
+ function InferredElementDragging(containerEl) {
+ var _this = _super.call(this, containerEl) || this;
+ _this.shouldIgnoreMove = false;
+ _this.mirrorSelector = '';
+ _this.currentMirrorEl = null;
+ _this.handlePointerDown = function (ev) {
+ _this.emitter.trigger('pointerdown', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragstart right away. does not support delay or min-distance
+ _this.emitter.trigger('dragstart', ev);
+ }
+ };
+ _this.handlePointerMove = function (ev) {
+ if (!_this.shouldIgnoreMove) {
+ _this.emitter.trigger('dragmove', ev);
+ }
+ };
+ _this.handlePointerUp = function (ev) {
+ _this.emitter.trigger('pointerup', ev);
+ if (!_this.shouldIgnoreMove) {
+ // fire dragend right away. does not support a revert animation
+ _this.emitter.trigger('dragend', ev);
+ }
+ };
+ var pointer = _this.pointer = new PointerDragging(containerEl);
+ pointer.emitter.on('pointerdown', _this.handlePointerDown);
+ pointer.emitter.on('pointermove', _this.handlePointerMove);
+ pointer.emitter.on('pointerup', _this.handlePointerUp);
+ return _this;
+ }
+ InferredElementDragging.prototype.destroy = function () {
+ this.pointer.destroy();
+ };
+ InferredElementDragging.prototype.setIgnoreMove = function (bool) {
+ this.shouldIgnoreMove = bool;
+ };
+ InferredElementDragging.prototype.setMirrorIsVisible = function (bool) {
+ if (bool) {
+ // restore a previously hidden element.
+ // use the reference in case the selector class has already been removed.
+ if (this.currentMirrorEl) {
+ this.currentMirrorEl.style.visibility = '';
+ this.currentMirrorEl = null;
+ }
+ }
+ else {
+ var mirrorEl = this.mirrorSelector
+ // TODO: somehow query FullCalendars WITHIN shadow-roots
+ ? document.querySelector(this.mirrorSelector)
+ : null;
+ if (mirrorEl) {
+ this.currentMirrorEl = mirrorEl;
+ mirrorEl.style.visibility = 'hidden';
+ }
+ }
+ };
+ return InferredElementDragging;
+ }(ElementDragging));
+
+ /*
+ Bridges third-party drag-n-drop systems with FullCalendar.
+ Must be instantiated and destroyed by caller.
+ */
+ var ThirdPartyDraggable = /** @class */ (function () {
+ function ThirdPartyDraggable(containerOrSettings, settings) {
+ var containerEl = document;
+ if (
+ // wish we could just test instanceof EventTarget, but doesn't work in IE11
+ containerOrSettings === document ||
+ containerOrSettings instanceof Element) {
+ containerEl = containerOrSettings;
+ settings = settings || {};
+ }
+ else {
+ settings = (containerOrSettings || {});
+ }
+ var dragging = this.dragging = new InferredElementDragging(containerEl);
+ if (typeof settings.itemSelector === 'string') {
+ dragging.pointer.selector = settings.itemSelector;
+ }
+ else if (containerEl === document) {
+ dragging.pointer.selector = '[data-event]';
+ }
+ if (typeof settings.mirrorSelector === 'string') {
+ dragging.mirrorSelector = settings.mirrorSelector;
+ }
+ new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
+ }
+ ThirdPartyDraggable.prototype.destroy = function () {
+ this.dragging.destroy();
+ };
+ return ThirdPartyDraggable;
+ }());
+
+ var interactionPlugin = createPlugin({
+ componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing],
+ calendarInteractions: [UnselectAuto],
+ elementDraggingImpl: FeaturefulElementDragging,
+ optionRefiners: OPTION_REFINERS$5,
+ listenerRefiners: LISTENER_REFINERS$1,
+ });
+
+ /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells.
+ ----------------------------------------------------------------------------------------------------------------------*/
+ // It is a manager for a Table subcomponent, which does most of the heavy lifting.
+ // It is responsible for managing width/height.
+ var TableView = /** @class */ (function (_super) {
+ __extends(TableView, _super);
+ function TableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.headerElRef = createRef();
+ return _this;
+ }
+ TableView.prototype.renderSimpleLayout = function (headerRowContent, bodyContent) {
+ var _a = this, props = _a.props, context = _a.context;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunk: { content: bodyContent },
+ });
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); }));
+ };
+ TableView.prototype.renderHScrollLayout = function (headerRowContent, bodyContent, colCnt, dayMinWidth) {
+ var ScrollGrid = this.context.pluginHooks.scrollGridImpl;
+ if (!ScrollGrid) {
+ throw new Error('No ScrollGrid implementation');
+ }
+ var _a = this, props = _a.props, context = _a.context;
+ var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
+ var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
+ var sections = [];
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunks: [{
+ key: 'main',
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ }],
+ });
+ }
+ sections.push({
+ type: 'body',
+ key: 'body',
+ liquid: true,
+ chunks: [{
+ key: 'main',
+ content: bodyContent,
+ }],
+ });
+ if (stickyFooterScrollbar) {
+ sections.push({
+ type: 'footer',
+ key: 'footer',
+ isSticky: true,
+ chunks: [{
+ key: 'main',
+ content: renderScrollShim,
+ }],
+ });
+ }
+ return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') },
+ createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); }));
+ };
+ return TableView;
+ }(DateComponent));
+
+ function splitSegsByRow(segs, rowCnt) {
+ var byRow = [];
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = [];
+ }
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ byRow[seg.row].push(seg);
+ }
+ return byRow;
+ }
+ function splitSegsByFirstCol(segs, colCnt) {
+ var byCol = [];
+ for (var i = 0; i < colCnt; i += 1) {
+ byCol[i] = [];
+ }
+ for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) {
+ var seg = segs_2[_i];
+ byCol[seg.firstCol].push(seg);
+ }
+ return byCol;
+ }
+ function splitInteractionByRow(ui, rowCnt) {
+ var byRow = [];
+ if (!ui) {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = null;
+ }
+ }
+ else {
+ for (var i = 0; i < rowCnt; i += 1) {
+ byRow[i] = {
+ affectedInstances: ui.affectedInstances,
+ isEvent: ui.isEvent,
+ segs: [],
+ };
+ }
+ for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) {
+ var seg = _a[_i];
+ byRow[seg.row].segs.push(seg);
+ }
+ }
+ return byRow;
+ }
+
+ var TableCellTop = /** @class */ (function (_super) {
+ __extends(TableCellTop, _super);
+ function TableCellTop() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableCellTop.prototype.render = function () {
+ var props = this.props;
+ var navLinkAttrs = buildNavLinkAttrs(this.context, props.date);
+ return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, defaultContent: renderTopInner }, function (innerElRef, innerContent) { return ((innerContent || props.forceDayTop) && (createElement("div", { className: "fc-daygrid-day-top", ref: innerElRef },
+ createElement("a", __assign({ id: props.dayNumberId, className: "fc-daygrid-day-number" }, navLinkAttrs), innerContent || createElement(Fragment, null, "\u00A0"))))); }));
+ };
+ return TableCellTop;
+ }(BaseComponent));
+ function renderTopInner(props) {
+ return props.dayNumberText;
+ }
+
+ var DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'narrow',
+ });
+ function hasListItemDisplay(seg) {
+ var display = seg.eventRange.ui.display;
+ return display === 'list-item' || (display === 'auto' &&
+ !seg.eventRange.def.allDay &&
+ seg.firstCol === seg.lastCol && // can't be multi-day
+ seg.isStart && // "
+ seg.isEnd // "
+ );
+ }
+
+ var TableBlockEvent = /** @class */ (function (_super) {
+ __extends(TableBlockEvent, _super);
+ function TableBlockEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableBlockEvent.prototype.render = function () {
+ var props = this.props;
+ return (createElement(StandardEvent, __assign({}, props, { extraClassNames: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay })));
+ };
+ return TableBlockEvent;
+ }(BaseComponent));
+
+ var TableListItemEvent = /** @class */ (function (_super) {
+ __extends(TableListItemEvent, _super);
+ function TableListItemEvent() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TableListItemEvent.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ var timeFormat = context.options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT;
+ var timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd);
+ return (createElement(EventRoot, { seg: props.seg, timeText: timeText, defaultContent: renderInnerContent$4, isDragging: props.isDragging, isResizing: false, isDateSelecting: false, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent) { return ( // we don't use styles!
+ createElement("a", __assign({ className: ['fc-daygrid-event', 'fc-daygrid-dot-event'].concat(classNames).join(' '), ref: rootElRef }, getSegAnchorAttrs(props.seg, context)), innerContent)); }));
+ };
+ return TableListItemEvent;
+ }(BaseComponent));
+ function renderInnerContent$4(innerProps) {
+ return (createElement(Fragment, null,
+ createElement("div", { className: "fc-daygrid-event-dot", style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }),
+ innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
+ createElement("div", { className: "fc-event-title" }, innerProps.event.title || createElement(Fragment, null, "\u00A0"))));
+ }
+
+ var TableCellMoreLink = /** @class */ (function (_super) {
+ __extends(TableCellMoreLink, _super);
+ function TableCellMoreLink() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.compileSegs = memoize(compileSegs);
+ return _this;
+ }
+ TableCellMoreLink.prototype.render = function () {
+ var props = this.props;
+ var _a = this.compileSegs(props.singlePlacements), allSegs = _a.allSegs, invisibleSegs = _a.invisibleSegs;
+ return (createElement(MoreLinkRoot, { dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: function () {
+ var isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
+ (props.eventResize ? props.eventResize.affectedInstances : null) ||
+ {};
+ return (createElement(Fragment, null, allSegs.map(function (seg) {
+ var instanceId = seg.eventRange.instance.instanceId;
+ return (createElement("div", { className: "fc-daygrid-event-harness", key: instanceId, style: {
+ visibility: isForcedInvisible[instanceId] ? 'hidden' : '',
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange))))));
+ })));
+ } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick, title, isExpanded, popoverId) { return (createElement("a", __assign({ ref: rootElRef, className: ['fc-daygrid-more-link'].concat(classNames).join(' '), title: title, "aria-expanded": isExpanded, "aria-controls": popoverId }, createAriaClickAttrs(handleClick)), innerContent)); }));
+ };
+ return TableCellMoreLink;
+ }(BaseComponent));
+ function compileSegs(singlePlacements) {
+ var allSegs = [];
+ var invisibleSegs = [];
+ for (var _i = 0, singlePlacements_1 = singlePlacements; _i < singlePlacements_1.length; _i++) {
+ var placement = singlePlacements_1[_i];
+ allSegs.push(placement.seg);
+ if (!placement.isVisible) {
+ invisibleSegs.push(placement.seg);
+ }
+ }
+ return { allSegs: allSegs, invisibleSegs: invisibleSegs };
+ }
+
+ var DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' });
+ var TableCell = /** @class */ (function (_super) {
+ __extends(TableCell, _super);
+ function TableCell() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.rootElRef = createRef();
+ _this.state = {
+ dayNumberId: getUniqueDomId(),
+ };
+ _this.handleRootEl = function (el) {
+ setRef(_this.rootElRef, el);
+ setRef(_this.props.elRef, el);
+ };
+ return _this;
+ }
+ TableCell.prototype.render = function () {
+ var _a = this, context = _a.context, props = _a.props, state = _a.state, rootElRef = _a.rootElRef;
+ var date = props.date, dateProfile = props.dateProfile;
+ var navLinkAttrs = buildNavLinkAttrs(context, date, 'week');
+ return (createElement(DayCellRoot, { date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, elRef: this.handleRootEl }, function (dayElRef, dayClassNames, rootDataAttrs, isDisabled) { return (createElement("td", __assign({ ref: dayElRef, role: "gridcell", className: ['fc-daygrid-day'].concat(dayClassNames, props.extraClassNames || []).join(' ') }, rootDataAttrs, props.extraDataAttrs, (props.showDayNumber ? { 'aria-labelledby': state.dayNumberId } : {})),
+ createElement("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef /* different from hook system! RENAME */ },
+ props.showWeekNumber && (createElement(WeekNumberRoot, { date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 }, function (weekElRef, weekClassNames, innerElRef, innerContent) { return (createElement("a", __assign({ ref: weekElRef, className: ['fc-daygrid-week-number'].concat(weekClassNames).join(' ') }, navLinkAttrs), innerContent)); })),
+ !isDisabled && (createElement(TableCellTop, { date: date, dateProfile: dateProfile, showDayNumber: props.showDayNumber, dayNumberId: state.dayNumberId, forceDayTop: props.forceDayTop, todayRange: props.todayRange, extraHookProps: props.extraHookProps })),
+ createElement("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef },
+ props.fgContent,
+ createElement("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } },
+ createElement(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))),
+ createElement("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))); }));
+ };
+ return TableCell;
+ }(DateComponent));
+
+ function computeFgSegPlacement(segs, // assumed already sorted
+ dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) {
+ var hierarchy = new DayGridSegHierarchy();
+ hierarchy.allowReslicing = true;
+ hierarchy.strictOrder = strictOrder;
+ if (dayMaxEvents === true || dayMaxEventRows === true) {
+ hierarchy.maxCoord = maxContentHeight;
+ hierarchy.hiddenConsumes = true;
+ }
+ else if (typeof dayMaxEvents === 'number') {
+ hierarchy.maxStackCnt = dayMaxEvents;
+ }
+ else if (typeof dayMaxEventRows === 'number') {
+ hierarchy.maxStackCnt = dayMaxEventRows;
+ hierarchy.hiddenConsumes = true;
+ }
+ // create segInputs only for segs with known heights
+ var segInputs = [];
+ var unknownHeightSegs = [];
+ for (var i = 0; i < segs.length; i += 1) {
+ var seg = segs[i];
+ var instanceId = seg.eventRange.instance.instanceId;
+ var eventHeight = eventInstanceHeights[instanceId];
+ if (eventHeight != null) {
+ segInputs.push({
+ index: i,
+ thickness: eventHeight,
+ span: {
+ start: seg.firstCol,
+ end: seg.lastCol + 1,
+ },
+ });
+ }
+ else {
+ unknownHeightSegs.push(seg);
+ }
+ }
+ var hiddenEntries = hierarchy.addSegs(segInputs);
+ var segRects = hierarchy.toRects();
+ var _a = placeRects(segRects, segs, cells), singleColPlacements = _a.singleColPlacements, multiColPlacements = _a.multiColPlacements, leftoverMargins = _a.leftoverMargins;
+ var moreCnts = [];
+ var moreMarginTops = [];
+ // add segs with unknown heights
+ for (var _i = 0, unknownHeightSegs_1 = unknownHeightSegs; _i < unknownHeightSegs_1.length; _i++) {
+ var seg = unknownHeightSegs_1[_i];
+ multiColPlacements[seg.firstCol].push({
+ seg: seg,
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = seg.firstCol; col <= seg.lastCol; col += 1) {
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // add the hidden entries
+ for (var col = 0; col < cells.length; col += 1) {
+ moreCnts.push(0);
+ }
+ for (var _b = 0, hiddenEntries_1 = hiddenEntries; _b < hiddenEntries_1.length; _b++) {
+ var hiddenEntry = hiddenEntries_1[_b];
+ var seg = segs[hiddenEntry.index];
+ var hiddenSpan = hiddenEntry.span;
+ multiColPlacements[hiddenSpan.start].push({
+ seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells),
+ isVisible: false,
+ isAbsolute: true,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ for (var col = hiddenSpan.start; col < hiddenSpan.end; col += 1) {
+ moreCnts[col] += 1;
+ singleColPlacements[col].push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: false,
+ isAbsolute: false,
+ absoluteTop: 0,
+ marginTop: 0,
+ });
+ }
+ }
+ // deal with leftover margins
+ for (var col = 0; col < cells.length; col += 1) {
+ moreMarginTops.push(leftoverMargins[col]);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, moreCnts: moreCnts, moreMarginTops: moreMarginTops };
+ }
+ // rects ordered by top coord, then left
+ function placeRects(allRects, segs, cells) {
+ var rectsByEachCol = groupRectsByEachCol(allRects, cells.length);
+ var singleColPlacements = [];
+ var multiColPlacements = [];
+ var leftoverMargins = [];
+ for (var col = 0; col < cells.length; col += 1) {
+ var rects = rectsByEachCol[col];
+ // compute all static segs in singlePlacements
+ var singlePlacements = [];
+ var currentHeight = 0;
+ var currentMarginTop = 0;
+ for (var _i = 0, rects_1 = rects; _i < rects_1.length; _i++) {
+ var rect = rects_1[_i];
+ var seg = segs[rect.index];
+ singlePlacements.push({
+ seg: resliceSeg(seg, col, col + 1, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: rect.levelCoord - currentHeight,
+ });
+ currentHeight = rect.levelCoord + rect.thickness;
+ }
+ // compute mixed static/absolute segs in multiPlacements
+ var multiPlacements = [];
+ currentHeight = 0;
+ currentMarginTop = 0;
+ for (var _a = 0, rects_2 = rects; _a < rects_2.length; _a++) {
+ var rect = rects_2[_a];
+ var seg = segs[rect.index];
+ var isAbsolute = rect.span.end - rect.span.start > 1; // multi-column?
+ var isFirstCol = rect.span.start === col;
+ currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg
+ currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg
+ if (isAbsolute) {
+ currentMarginTop += rect.thickness;
+ if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: rect.levelCoord,
+ marginTop: 0,
+ });
+ }
+ }
+ else if (isFirstCol) {
+ multiPlacements.push({
+ seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
+ isVisible: true,
+ isAbsolute: false,
+ absoluteTop: rect.levelCoord,
+ marginTop: currentMarginTop, // claim the margin
+ });
+ currentMarginTop = 0;
+ }
+ }
+ singleColPlacements.push(singlePlacements);
+ multiColPlacements.push(multiPlacements);
+ leftoverMargins.push(currentMarginTop);
+ }
+ return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, leftoverMargins: leftoverMargins };
+ }
+ function groupRectsByEachCol(rects, colCnt) {
+ var rectsByEachCol = [];
+ for (var col = 0; col < colCnt; col += 1) {
+ rectsByEachCol.push([]);
+ }
+ for (var _i = 0, rects_3 = rects; _i < rects_3.length; _i++) {
+ var rect = rects_3[_i];
+ for (var col = rect.span.start; col < rect.span.end; col += 1) {
+ rectsByEachCol[col].push(rect);
+ }
+ }
+ return rectsByEachCol;
+ }
+ function resliceSeg(seg, spanStart, spanEnd, cells) {
+ if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
+ return seg;
+ }
+ var eventRange = seg.eventRange;
+ var origRange = eventRange.range;
+ var slicedRange = intersectRanges(origRange, {
+ start: cells[spanStart].date,
+ end: addDays(cells[spanEnd - 1].date, 1),
+ });
+ return __assign(__assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: {
+ def: eventRange.def,
+ ui: __assign(__assign({}, eventRange.ui), { durationEditable: false }),
+ instance: eventRange.instance,
+ range: slicedRange,
+ }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() });
+ }
+ var DayGridSegHierarchy = /** @class */ (function (_super) {
+ __extends(DayGridSegHierarchy, _super);
+ function DayGridSegHierarchy() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ // config
+ _this.hiddenConsumes = false;
+ // allows us to keep hidden entries in the hierarchy so they take up space
+ _this.forceHidden = {};
+ return _this;
+ }
+ DayGridSegHierarchy.prototype.addSegs = function (segInputs) {
+ var _this = this;
+ var hiddenSegs = _super.prototype.addSegs.call(this, segInputs);
+ var entriesByLevel = this.entriesByLevel;
+ var excludeHidden = function (entry) { return !_this.forceHidden[buildEntryKey(entry)]; };
+ // remove the forced-hidden segs
+ for (var level = 0; level < entriesByLevel.length; level += 1) {
+ entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden);
+ }
+ return hiddenSegs;
+ };
+ DayGridSegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) {
+ var _a = this, entriesByLevel = _a.entriesByLevel, forceHidden = _a.forceHidden;
+ var touchingEntry = insertion.touchingEntry, touchingLevel = insertion.touchingLevel, touchingLateral = insertion.touchingLateral;
+ if (this.hiddenConsumes && touchingEntry) {
+ var touchingEntryId = buildEntryKey(touchingEntry);
+ // if not already hidden
+ if (!forceHidden[touchingEntryId]) {
+ if (this.allowReslicing) {
+ var placeholderEntry = __assign(__assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) });
+ var placeholderEntryId = buildEntryKey(placeholderEntry);
+ forceHidden[placeholderEntryId] = true;
+ entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder
+ this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it
+ }
+ else {
+ forceHidden[touchingEntryId] = true;
+ hiddenEntries.push(touchingEntry);
+ }
+ }
+ }
+ return _super.prototype.handleInvalidInsertion.call(this, insertion, entry, hiddenEntries);
+ };
+ return DayGridSegHierarchy;
+ }(SegHierarchy));
+
+ var TableRow = /** @class */ (function (_super) {
+ __extends(TableRow, _super);
+ function TableRow() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.cellElRefs = new RefMap(); // the
+ _this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame
+ _this.fgElRefs = new RefMap(); // the fc-daygrid-day-events
+ _this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol"
+ _this.rootElRef = createRef();
+ _this.state = {
+ framePositions: null,
+ maxContentHeight: null,
+ eventInstanceHeights: {},
+ };
+ return _this;
+ }
+ TableRow.prototype.render = function () {
+ var _this = this;
+ var _a = this, props = _a.props, state = _a.state, context = _a.context;
+ var options = context.options;
+ var colCnt = props.cells.length;
+ var businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt);
+ var bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt);
+ var highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt);
+ var mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt);
+ var _b = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells), singleColPlacements = _b.singleColPlacements, multiColPlacements = _b.multiColPlacements, moreCnts = _b.moreCnts, moreMarginTops = _b.moreMarginTops;
+ var isForcedInvisible = // TODO: messy way to compute this
+ (props.eventDrag && props.eventDrag.affectedInstances) ||
+ (props.eventResize && props.eventResize.affectedInstances) ||
+ {};
+ return (createElement("tr", { ref: this.rootElRef, role: "row" },
+ props.renderIntro && props.renderIntro(),
+ props.cells.map(function (cell, col) {
+ var normalFgNodes = _this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible);
+ var mirrorFgNodes = _this.renderFgSegs(col, buildMirrorPlacements$1(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false);
+ return (createElement(TableCell, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), innerElRef: _this.frameElRefs.createRef(cell.key) /* FF
problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: _this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ createElement(Fragment, null, normalFgNodes),
+ createElement(Fragment, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys
+ createElement(Fragment, null,
+ _this.renderFillSegs(highlightSegsByCol[col], 'highlight'),
+ _this.renderFillSegs(businessHoursByCol[col], 'non-business'),
+ _this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) }));
+ })));
+ };
+ TableRow.prototype.componentDidMount = function () {
+ this.updateSizing(true);
+ };
+ TableRow.prototype.componentDidUpdate = function (prevProps, prevState) {
+ var currentProps = this.props;
+ this.updateSizing(!isPropsEqual(prevProps, currentProps));
+ };
+ TableRow.prototype.getHighlightSegs = function () {
+ var props = this.props;
+ if (props.eventDrag && props.eventDrag.segs.length) { // messy check
+ return props.eventDrag.segs;
+ }
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return props.dateSelectionSegs;
+ };
+ TableRow.prototype.getMirrorSegs = function () {
+ var props = this.props;
+ if (props.eventResize && props.eventResize.segs.length) { // messy check
+ return props.eventResize.segs;
+ }
+ return [];
+ };
+ TableRow.prototype.renderFgSegs = function (col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
+ var context = this.context;
+ var eventSelection = this.props.eventSelection;
+ var framePositions = this.state.framePositions;
+ var defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1
+ var isMirror = isDragging || isResizing || isDateSelecting;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segPlacements_1 = segPlacements; _i < segPlacements_1.length; _i++) {
+ var placement = segPlacements_1[_i];
+ var seg = placement.seg;
+ var instanceId = seg.eventRange.instance.instanceId;
+ var key = instanceId + ':' + col;
+ var isVisible = placement.isVisible && !isForcedInvisible[instanceId];
+ var isAbsolute = placement.isAbsolute;
+ var left = '';
+ var right = '';
+ if (isAbsolute) {
+ if (context.isRtl) {
+ right = 0;
+ left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol];
+ }
+ else {
+ left = 0;
+ right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol];
+ }
+ }
+ /*
+ known bug: events that are force to be list-item but span multiple days still take up space in later columns
+ todo: in print view, for multi-day events, don't display title within non-start/end segs
+ */
+ nodes.push(createElement("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: {
+ visibility: isVisible ? '' : 'hidden',
+ marginTop: isAbsolute ? '' : placement.marginTop,
+ top: isAbsolute ? placement.absoluteTop : '',
+ left: left,
+ right: right,
+ } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange))))));
+ }
+ }
+ return nodes;
+ };
+ TableRow.prototype.renderFillSegs = function (segs, fillType) {
+ var isRtl = this.context.isRtl;
+ var todayRange = this.props.todayRange;
+ var framePositions = this.state.framePositions;
+ var nodes = [];
+ if (framePositions) {
+ for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) {
+ var seg = segs_1[_i];
+ var leftRightCss = isRtl ? {
+ right: 0,
+ left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol],
+ } : {
+ left: 0,
+ right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol],
+ };
+ nodes.push(createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ?
+ createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, todayRange))) :
+ renderFill(fillType)));
+ }
+ }
+ return createElement.apply(void 0, __spreadArray([Fragment, {}], nodes));
+ };
+ TableRow.prototype.updateSizing = function (isExternalSizingChange) {
+ var _a = this, props = _a.props, frameElRefs = _a.frameElRefs;
+ if (!props.forPrint &&
+ props.clientWidth !== null // positioning ready?
+ ) {
+ if (isExternalSizingChange) {
+ var frameEls = props.cells.map(function (cell) { return frameElRefs.currentMap[cell.key]; });
+ if (frameEls.length) {
+ var originEl = this.rootElRef.current;
+ this.setState({
+ framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal
+ false),
+ });
+ }
+ }
+ var oldInstanceHeights = this.state.eventInstanceHeights;
+ var newInstanceHeights = this.queryEventInstanceHeights();
+ var limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true;
+ this.setState({
+ // HACK to prevent oscillations of events being shown/hidden from max-event-rows
+ // Essentially, once you compute an element's height, never null-out.
+ // TODO: always display all events, as visibility:hidden?
+ eventInstanceHeights: __assign(__assign({}, oldInstanceHeights), newInstanceHeights),
+ maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null,
+ });
+ }
+ };
+ TableRow.prototype.queryEventInstanceHeights = function () {
+ var segElMap = this.segHarnessRefs.currentMap;
+ var eventInstanceHeights = {};
+ // get the max height amongst instance segs
+ for (var key in segElMap) {
+ var height = Math.round(segElMap[key].getBoundingClientRect().height);
+ var instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key
+ eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height);
+ }
+ return eventInstanceHeights;
+ };
+ TableRow.prototype.computeMaxContentHeight = function () {
+ var firstKey = this.props.cells[0].key;
+ var cellEl = this.cellElRefs.currentMap[firstKey];
+ var fcContainerEl = this.fgElRefs.currentMap[firstKey];
+ return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top;
+ };
+ TableRow.prototype.getCellEls = function () {
+ var elMap = this.cellElRefs.currentMap;
+ return this.props.cells.map(function (cell) { return elMap[cell.key]; });
+ };
+ return TableRow;
+ }(DateComponent));
+ TableRow.addStateEquality({
+ eventInstanceHeights: isPropsEqual,
+ });
+ function buildMirrorPlacements$1(mirrorSegs, colPlacements) {
+ if (!mirrorSegs.length) {
+ return [];
+ }
+ var topsByInstanceId = buildAbsoluteTopHash$1(colPlacements); // TODO: cache this at first render?
+ return mirrorSegs.map(function (seg) { return ({
+ seg: seg,
+ isVisible: true,
+ isAbsolute: true,
+ absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId],
+ marginTop: 0,
+ }); });
+ }
+ function buildAbsoluteTopHash$1(colPlacements) {
+ var topsByInstanceId = {};
+ for (var _i = 0, colPlacements_1 = colPlacements; _i < colPlacements_1.length; _i++) {
+ var placements = colPlacements_1[_i];
+ for (var _a = 0, placements_1 = placements; _a < placements_1.length; _a++) {
+ var placement = placements_1[_a];
+ topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop;
+ }
+ }
+ return topsByInstanceId;
+ }
+
+ var Table = /** @class */ (function (_super) {
+ __extends(Table, _super);
+ function Table() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.splitBusinessHourSegs = memoize(splitSegsByRow);
+ _this.splitBgEventSegs = memoize(splitSegsByRow);
+ _this.splitFgEventSegs = memoize(splitSegsByRow);
+ _this.splitDateSelectionSegs = memoize(splitSegsByRow);
+ _this.splitEventDrag = memoize(splitInteractionByRow);
+ _this.splitEventResize = memoize(splitInteractionByRow);
+ _this.rowRefs = new RefMap();
+ _this.handleRootEl = function (rootEl) {
+ _this.rootEl = rootEl;
+ if (rootEl) {
+ _this.context.registerInteractiveComponent(_this, {
+ el: rootEl,
+ isHitComboAllowed: _this.props.isHitComboAllowed,
+ });
+ }
+ else {
+ _this.context.unregisterInteractiveComponent(_this);
+ }
+ };
+ return _this;
+ }
+ Table.prototype.render = function () {
+ var _this = this;
+ var props = this.props;
+ var dateProfile = props.dateProfile, dayMaxEventRows = props.dayMaxEventRows, dayMaxEvents = props.dayMaxEvents, expandRows = props.expandRows;
+ var rowCnt = props.cells.length;
+ var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt);
+ var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt);
+ var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt);
+ var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt);
+ var eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt);
+ var eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt);
+ var limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true;
+ // if rows can't expand to fill fixed height, can't do balanced-height event limit
+ // TODO: best place to normalize these options?
+ if (limitViaBalanced && !expandRows) {
+ limitViaBalanced = false;
+ dayMaxEventRows = null;
+ dayMaxEvents = null;
+ }
+ var classNames = [
+ 'fc-daygrid-body',
+ limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced',
+ expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others?
+ ];
+ return (createElement("div", { className: classNames.join(' '), ref: this.handleRootEl, style: {
+ // these props are important to give this wrapper correct dimensions for interactions
+ // TODO: if we set it here, can we avoid giving to inner tables?
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ } },
+ createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement(Fragment, null,
+ createElement("table", { role: "presentation", className: "fc-scrollgrid-sync-table", style: {
+ width: props.clientWidth,
+ minWidth: props.tableMinWidth,
+ height: expandRows ? props.clientHeight : '',
+ } },
+ props.colGroupNode,
+ createElement("tbody", { role: "presentation" }, props.cells.map(function (cells, row) { return (createElement(TableRow, { ref: _this.rowRefs.createRef(row), key: cells.length
+ ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */
+ : row // in case there are no cells (like when resource view is loading)
+ , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })); }))))); })));
+ };
+ // Hit System
+ // ----------------------------------------------------------------------------------------------------
+ Table.prototype.prepareHits = function () {
+ this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map(function (rowObj) { return rowObj.getCellEls()[0]; }), // first cell el in each row. TODO: not optimal
+ false, true);
+ this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row
+ true, // horizontal
+ false);
+ };
+ Table.prototype.queryHit = function (positionLeft, positionTop) {
+ var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions;
+ var col = colPositions.leftToIndex(positionLeft);
+ var row = rowPositions.topToIndex(positionTop);
+ if (row != null && col != null) {
+ var cell = this.props.cells[row][col];
+ return {
+ dateProfile: this.props.dateProfile,
+ dateSpan: __assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan),
+ dayEl: this.getCellEl(row, col),
+ rect: {
+ left: colPositions.lefts[col],
+ right: colPositions.rights[col],
+ top: rowPositions.tops[row],
+ bottom: rowPositions.bottoms[row],
+ },
+ layer: 0,
+ };
+ }
+ return null;
+ };
+ Table.prototype.getCellEl = function (row, col) {
+ return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal
+ };
+ Table.prototype.getCellRange = function (row, col) {
+ var start = this.props.cells[row][col].date;
+ var end = addDays(start, 1);
+ return { start: start, end: end };
+ };
+ return Table;
+ }(DateComponent));
+ function isSegAllDay(seg) {
+ return seg.eventRange.def.allDay;
+ }
+
+ var DayTableSlicer = /** @class */ (function (_super) {
+ __extends(DayTableSlicer, _super);
+ function DayTableSlicer() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.forceDayIfListItem = true;
+ return _this;
+ }
+ DayTableSlicer.prototype.sliceRange = function (dateRange, dayTableModel) {
+ return dayTableModel.sliceRange(dateRange);
+ };
+ return DayTableSlicer;
+ }(Slicer));
+
+ var DayTable = /** @class */ (function (_super) {
+ __extends(DayTable, _super);
+ function DayTable() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.slicer = new DayTableSlicer();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTable.prototype.render = function () {
+ var _a = this, props = _a.props, context = _a.context;
+ return (createElement(Table, __assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })));
+ };
+ return DayTable;
+ }(DateComponent));
+
+ var DayTableView = /** @class */ (function (_super) {
+ __extends(DayTableView, _super);
+ function DayTableView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.buildDayTableModel = memoize(buildDayTableModel);
+ _this.headerRef = createRef();
+ _this.tableRef = createRef();
+ return _this;
+ }
+ DayTableView.prototype.render = function () {
+ var _this = this;
+ var _a = this.context, options = _a.options, dateProfileGenerator = _a.dateProfileGenerator;
+ var props = this.props;
+ var dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator);
+ var headerContent = options.dayHeaders && (createElement(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 }));
+ var bodyContent = function (contentArg) { return (createElement(DayTable, { ref: _this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint })); };
+ return options.dayMinWidth
+ ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth)
+ : this.renderSimpleLayout(headerContent, bodyContent);
+ };
+ return DayTableView;
+ }(TableView));
+ function buildDayTableModel(dateProfile, dateProfileGenerator) {
+ var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
+ return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit));
+ }
+
+ var TableDateProfileGenerator = /** @class */ (function (_super) {
+ __extends(TableDateProfileGenerator, _super);
+ function TableDateProfileGenerator() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ // Computes the date range that will be rendered.
+ TableDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) {
+ var dateEnv = this.props.dateEnv;
+ var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay);
+ var start = renderRange.start;
+ var end = renderRange.end;
+ var endOfWeek;
+ // year and month views should be aligned with weeks. this is already done for week
+ if (/^(year|month)$/.test(currentRangeUnit)) {
+ start = dateEnv.startOfWeek(start);
+ // make end-of-week if not already
+ endOfWeek = dateEnv.startOfWeek(end);
+ if (endOfWeek.valueOf() !== end.valueOf()) {
+ end = addWeeks(endOfWeek, 1);
+ }
+ }
+ // ensure 6 weeks
+ if (this.props.monthMode &&
+ this.props.fixedWeekCount) {
+ var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays
+ diffWeeks(start, end));
+ end = addWeeks(end, 6 - rowCnt);
+ }
+ return { start: start, end: end };
+ };
+ return TableDateProfileGenerator;
+ }(DateProfileGenerator));
+
+ var dayGridPlugin = createPlugin({
+ initialView: 'dayGridMonth',
+ views: {
+ dayGrid: {
+ component: DayTableView,
+ dateProfileGeneratorClass: TableDateProfileGenerator,
+ },
+ dayGridDay: {
+ type: 'dayGrid',
+ duration: { days: 1 },
+ },
+ dayGridWeek: {
+ type: 'dayGrid',
+ duration: { weeks: 1 },
+ },
+ dayGridMonth: {
+ type: 'dayGrid',
+ duration: { months: 1 },
+ monthMode: true,
+ fixedWeekCount: true,
+ },
+ },
+ });
+
+ var AllDaySplitter = /** @class */ (function (_super) {
+ __extends(AllDaySplitter, _super);
+ function AllDaySplitter() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ AllDaySplitter.prototype.getKeyInfo = function () {
+ return {
+ allDay: {},
+ timed: {},
+ };
+ };
+ AllDaySplitter.prototype.getKeysForDateSpan = function (dateSpan) {
+ if (dateSpan.allDay) {
+ return ['allDay'];
+ }
+ return ['timed'];
+ };
+ AllDaySplitter.prototype.getKeysForEventDef = function (eventDef) {
+ if (!eventDef.allDay) {
+ return ['timed'];
+ }
+ if (hasBgRendering(eventDef)) {
+ return ['timed', 'allDay'];
+ }
+ return ['allDay'];
+ };
+ return AllDaySplitter;
+ }(Splitter));
+
+ var DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
+ hour: 'numeric',
+ minute: '2-digit',
+ omitZeroMinute: true,
+ meridiem: 'short',
+ });
+ function TimeColsAxisCell(props) {
+ var classNames = [
+ 'fc-timegrid-slot',
+ 'fc-timegrid-slot-label',
+ props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
+ ];
+ return (createElement(ViewContextType.Consumer, null, function (context) {
+ if (!props.isLabeled) {
+ return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
+ }
+ var dateEnv = context.dateEnv, options = context.options, viewApi = context.viewApi;
+ var labelFormat = // TODO: fully pre-parse
+ options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
+ Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
+ createFormatter(options.slotLabelFormat);
+ var hookProps = {
+ level: 0,
+ time: props.time,
+ date: dateEnv.toDate(props.date),
+ view: viewApi,
+ text: dateEnv.format(props.date, labelFormat),
+ };
+ return (createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLabelClassNames, content: options.slotLabelContent, defaultContent: renderInnerContent$3, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": props.isoTimeStr },
+ createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
+ createElement("div", { className: "fc-timegrid-slot-label-cushion fc-scrollgrid-shrink-cushion", ref: innerElRef }, innerContent)))); }));
+ }));
+ }
+ function renderInnerContent$3(props) {
+ return props.text;
+ }
+
+ var TimeBodyAxis = /** @class */ (function (_super) {
+ __extends(TimeBodyAxis, _super);
+ function TimeBodyAxis() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ TimeBodyAxis.prototype.render = function () {
+ return this.props.slatMetas.map(function (slatMeta) { return (createElement("tr", { key: slatMeta.key },
+ createElement(TimeColsAxisCell, __assign({}, slatMeta)))); });
+ };
+ return TimeBodyAxis;
+ }(BaseComponent));
+
+ var DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
+ var AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
+ var TimeColsView = /** @class */ (function (_super) {
+ __extends(TimeColsView, _super);
+ function TimeColsView() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.allDaySplitter = new AllDaySplitter(); // for use by subclasses
+ _this.headerElRef = createRef();
+ _this.rootElRef = createRef();
+ _this.scrollerElRef = createRef();
+ _this.state = {
+ slatCoords: null,
+ };
+ _this.handleScrollTopRequest = function (scrollTop) {
+ var scrollerEl = _this.scrollerElRef.current;
+ if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer
+ scrollerEl.scrollTop = scrollTop;
+ }
+ };
+ /* Header Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ _this.renderHeadAxis = function (rowKey, frameHeight) {
+ if (frameHeight === void 0) { frameHeight = ''; }
+ var options = _this.context.options;
+ var dateProfile = _this.props.dateProfile;
+ var range = dateProfile.renderRange;
+ var dayCnt = diffDays(range.start, range.end);
+ var navLinkAttrs = (dayCnt === 1) // only do in day views (to avoid doing in week views that dont need it)
+ ? buildNavLinkAttrs(_this.context, range.start, 'week')
+ : {};
+ if (options.weekNumbers && rowKey === 'day') {
+ return (createElement(WeekNumberRoot, { date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("th", { ref: rootElRef, "aria-hidden": true, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: "fc-timegrid-axis-frame fc-scrollgrid-shrink-frame fc-timegrid-axis-frame-liquid", style: { height: frameHeight } },
+ createElement("a", __assign({ ref: innerElRef, className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner" }, navLinkAttrs), innerContent)))); }));
+ }
+ return (createElement("th", { "aria-hidden": true, className: "fc-timegrid-axis" },
+ createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
+ };
+ /* Table Component Render Methods
+ ------------------------------------------------------------------------------------------------------------------*/
+ // only a one-way height sync. we don't send the axis inner-content height to the DayGrid,
+ // but DayGrid still needs to have classNames on inner elements in order to measure.
+ _this.renderTableRowAxis = function (rowHeight) {
+ var _a = _this.context, options = _a.options, viewApi = _a.viewApi;
+ var hookProps = {
+ text: options.allDayText,
+ view: viewApi,
+ };
+ return (
+ // TODO: make reusable hook. used in list view too
+ createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner$1, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, "aria-hidden": true, className: [
+ 'fc-timegrid-axis',
+ 'fc-scrollgrid-shrink',
+ ].concat(classNames).join(' ') },
+ createElement("div", { className: 'fc-timegrid-axis-frame fc-scrollgrid-shrink-frame' + (rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : ''), style: { height: rowHeight } },
+ createElement("span", { className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner", ref: innerElRef }, innerContent)))); }));
+ };
+ _this.handleSlatCoords = function (slatCoords) {
+ _this.setState({ slatCoords: slatCoords });
+ };
+ return _this;
+ }
+ // rendering
+ // ----------------------------------------------------------------------------------------------------
+ TimeColsView.prototype.renderSimpleLayout = function (headerRowContent, allDayContent, timeContent) {
+ var _a = this, context = _a.context, props = _a.props;
+ var sections = [];
+ var stickyHeaderDates = getStickyHeaderDates(context.options);
+ if (headerRowContent) {
+ sections.push({
+ type: 'header',
+ key: 'header',
+ isSticky: stickyHeaderDates,
+ chunk: {
+ elRef: this.headerElRef,
+ tableClassName: 'fc-col-header',
+ rowContent: headerRowContent,
+ },
+ });
+ }
+ if (allDayContent) {
+ sections.push({
+ type: 'body',
+ key: 'all-day',
+ chunk: { content: allDayContent },
+ });
+ sections.push({
+ type: 'body',
+ key: 'all-day-divider',
+ outerContent: ( // TODO: rename to cellContent so don't need to define
Aquí publicaremos todas las novedades que necesitarás conocer para disfrutar al 100% de la conferencia. Permanece atento a nuestras publicaciones, pues aquí vamos a presentar las novedades y anuncios sobre la agenda de la conferencia, las ayudas y becas de esta edición, adicionalmente a la información sobre la venta de entradas.