From 2bde60e285e98c5b8a6a097ad23fe83c895a1d87 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 14 May 2024 13:16:24 +0100 Subject: [PATCH] Add web calendar --- calmerge/__init__.py | 8 +++++++- calmerge/static/calendar.css | 8 ++++++++ calmerge/static/calendar.js | 25 +++++++++++++++++++++++++ calmerge/templates.py | 4 ++-- calmerge/templates/calendar.html | 22 ++++++++++++++++++++++ calmerge/templates/listing.html | 32 ++++++++++++++++---------------- calmerge/views.py | 13 +++++++++++++ 7 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 calmerge/static/calendar.css create mode 100644 calmerge/static/calendar.js create mode 100644 calmerge/templates/calendar.html diff --git a/calmerge/__init__.py b/calmerge/__init__.py index aa9ea47..cd38bf2 100644 --- a/calmerge/__init__.py +++ b/calmerge/__init__.py @@ -1,3 +1,5 @@ +from pathlib import Path + import aiohttp_jinja2 from aiohttp import web from jinja2 import FileSystemLoader @@ -5,6 +7,8 @@ from . import templates, views from .config import Config +STATIC_DIR = Path(__file__).resolve().parent / "static" + def get_aiohttp_app(config: Config) -> web.Application: app = web.Application() @@ -18,14 +22,16 @@ def get_aiohttp_app(config: Config) -> web.Application: ], ) - jinja2_env.filters["webcal_url"] = templates.webcal_url + jinja2_env.filters["calendar_url"] = templates.calendar_url app["config"] = config app.add_routes( [ + web.static("/static", STATIC_DIR), web.get("/.health/", views.healthcheck, name="healthcheck"), web.get("/{slug}.ics", views.calendar, name="calendar"), + web.get("/{slug}.html", views.calendar_html, name="calendar-html"), ] ) diff --git a/calmerge/static/calendar.css b/calmerge/static/calendar.css new file mode 100644 index 0000000..0b2c838 --- /dev/null +++ b/calmerge/static/calendar.css @@ -0,0 +1,8 @@ +#calendar { + max-width: 75vw; + max-height: 75vh; + margin: 0 auto; +} +#calendar-message { + text-align: center; +} diff --git a/calmerge/static/calendar.js b/calmerge/static/calendar.js new file mode 100644 index 0000000..620b8dd --- /dev/null +++ b/calmerge/static/calendar.js @@ -0,0 +1,25 @@ +document.addEventListener('DOMContentLoaded', function() { + var calendarEl = document.getElementById('calendar'); + var calendarMessage = document.getElementById('calendar-message'); + + var calendar = new FullCalendar.Calendar(calendarEl, { + headerToolbar: { + start: 'title', + center: '', + end: 'today prev,next dayGridMonth,listYear,multiMonthYear' + }, + initialView: 'listYear', + events: { + url: calendarEl.dataset.url, + format: 'ics', + }, + eventSourceSuccess: function() { + calendarMessage.style.display = 'none'; + }, + eventSourceFailure: function() { + calendarMessage.innerText = "Loading the calendar failed. Please try again later, or check your browser's developer console for more details." + } + }); + + calendar.render(); + }); diff --git a/calmerge/templates.py b/calmerge/templates.py index 9609bd6..c6749c1 100644 --- a/calmerge/templates.py +++ b/calmerge/templates.py @@ -14,12 +14,12 @@ async def config_context_processor(request: web.Request) -> dict: @jinja2.pass_context -def webcal_url(context: dict, calendar_config: CalendarConfig) -> URL: +def calendar_url(context: dict, calendar_config: CalendarConfig) -> URL: request: web.Request = context["request"] config = context["config"] calendar_url = context["url"](context, "calendar", slug=calendar_config.slug) - url = request.url.with_scheme("webcal").with_path(calendar_url.path) + url = request.url.with_path(calendar_url.path) if config.listing.include_credentials and calendar_config.auth is not None: url = url.with_user(calendar_config.auth.username).with_password( diff --git a/calmerge/templates/calendar.html b/calmerge/templates/calendar.html new file mode 100644 index 0000000..ab0fbbe --- /dev/null +++ b/calmerge/templates/calendar.html @@ -0,0 +1,22 @@ +{% set calendar_name = calendar.name|default(calendar.slug, true) %} + + + + {{ calendar_name }} + + + + +

{{ calendar_name }}

+

{{ calendar.description }}

+

{{ calendar|calendar_url }}

+ +

Events are loading...

+
+ + + + + + + diff --git a/calmerge/templates/listing.html b/calmerge/templates/listing.html index 062eddc..755a100 100644 --- a/calmerge/templates/listing.html +++ b/calmerge/templates/listing.html @@ -25,26 +25,26 @@

Calmerge

Offsets TTL Auth + Preview + {% for calendar in config.calendars|sort(attribute="slug") %} - {% with calendar_url = url('calendar', slug=calendar.slug) %} - {{ calendar_url }} - {{ calendar.name|default("-", true) }} - {{ calendar.description|default("-", true) }} - {{ calendar.offset_days|sort|join(", ") }} - {{ calendar.ttl_hours }} hours - {% if config.listing.include_credentials and calendar.auth %} - {{ calendar.auth.username }}:{{ calendar.auth.password }} - {% else %} - {{ "Yes" if calendar.auth else "No" }} - {% endif %} - - {{ calendar_url.scheme|default("http", true) }} - webcal - - {% endwith %} + {{ calendar.slug }} + {{ calendar.name|default("-", true) }} + {{ calendar.description|default("-", true) }} + {{ calendar.offset_days|sort|join(", ") }} + {{ calendar.ttl_hours }} hours + {% if config.listing.include_credentials and calendar.auth %} + {{ calendar.auth.username }}:{{ calendar.auth.password }} + {% else %} + {{ "Yes" if calendar.auth else "No" }} + {% endif %} + preview + + {{ (calendar|calendar_url).scheme|default('http', true) }} + webcal + {% endfor %} diff --git a/calmerge/views.py b/calmerge/views.py index b5eb8be..632d72e 100644 --- a/calmerge/views.py +++ b/calmerge/views.py @@ -56,3 +56,16 @@ async def calendar_listing(request: web.Request) -> web.Response: "default-src 'self'; style-src 'unsafe-inline'" ) return response + + +async def calendar_html(request: web.Request) -> web.Response: + config = request.app["config"] + + calendar_config = config.get_calendar_by_slug(request.match_info["slug"]) + + if calendar_config is None: + raise web.HTTPNotFound() + + return aiohttp_jinja2.render_template( + "calendar.html", request, {"calendar": calendar_config} + )