Merge pull request #1070 from ae-utbm/calendar-link

Remote calendar link for external sync
This commit is contained in:
Bartuccio Antoine 2025-04-08 15:50:38 +02:00 committed by GitHub
commit 11efa4fca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 131 additions and 16 deletions

View File

@ -1,9 +1,11 @@
from pathlib import Path from pathlib import Path
from typing import final
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.syndication.views import add_domain
from django.db.models import F, QuerySet from django.db.models import F, QuerySet
from django.http import HttpRequest
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from ical.calendar import Calendar from ical.calendar import Calendar
@ -14,7 +16,14 @@ from com.models import NewsDate
from core.models import User from core.models import User
@final def as_absolute_url(url: str, request: HttpRequest | None = None) -> str:
return add_domain(
Site.objects.get_current(request=request),
url,
secure=request.is_secure() if request is not None else settings.HTTPS,
)
class IcsCalendar: class IcsCalendar:
_CACHE_FOLDER: Path = settings.MEDIA_ROOT / "com" / "calendars" _CACHE_FOLDER: Path = settings.MEDIA_ROOT / "com" / "calendars"
_INTERNAL_CALENDAR = _CACHE_FOLDER / "internal.ics" _INTERNAL_CALENDAR = _CACHE_FOLDER / "internal.ics"
@ -58,7 +67,9 @@ class IcsCalendar:
summary=news_date.news_title, summary=news_date.news_title,
start=news_date.start_date, start=news_date.start_date,
end=news_date.end_date, end=news_date.end_date,
url=reverse("com:news_detail", kwargs={"news_id": news_date.news.id}), url=as_absolute_url(
reverse("com:news_detail", kwargs={"news_id": news_date.news.id})
),
) )
calendar.events.append(event) calendar.events.append(event)

View File

@ -44,7 +44,18 @@ export class IcsCalendar extends inheritHtmlElement("div") {
return this.isMobile() ? "listMonth" : "dayGridMonth"; return this.isMobile() ? "listMonth" : "dayGridMonth";
} }
currentToolbar() { currentFooterToolbar() {
if (this.isMobile()) {
return {
start: "",
center: "getCalendarLink",
end: "",
};
}
return { start: "getCalendarLink", center: "", end: "" };
}
currentHeaderToolbar() {
if (this.isMobile()) { if (this.isMobile()) {
return { return {
left: "prev,next", left: "prev,next",
@ -303,14 +314,44 @@ export class IcsCalendar extends inheritHtmlElement("div") {
this.calendar = new Calendar(this.node, { this.calendar = new Calendar(this.node, {
plugins: [dayGridPlugin, iCalendarPlugin, listPlugin], plugins: [dayGridPlugin, iCalendarPlugin, listPlugin],
locales: [frLocale, enLocale], locales: [frLocale, enLocale],
customButtons: {
getCalendarLink: {
text: gettext("Copy calendar link"),
click: async (event: Event) => {
const button = event.target as HTMLButtonElement;
button.classList.add("text-copy");
if (!button.hasAttribute("position")) {
button.setAttribute("tooltip", gettext("Link copied"));
button.setAttribute("position", "top");
button.setAttribute("no-hover", "");
}
if (button.classList.contains("text-copied")) {
button.classList.remove("text-copied");
}
navigator.clipboard.writeText(
new URL(
await makeUrl(calendarCalendarInternal),
window.location.origin,
).toString(),
);
setTimeout(() => {
button.classList.remove("text-copied");
button.classList.add("text-copied");
button.classList.remove("text-copy");
}, 1500);
},
},
},
height: "auto", height: "auto",
locale: this.locale, locale: this.locale,
initialView: this.currentView(), initialView: this.currentView(),
headerToolbar: this.currentToolbar(), headerToolbar: this.currentHeaderToolbar(),
footerToolbar: this.currentFooterToolbar(),
eventSources: await this.getEventSources(), eventSources: await this.getEventSources(),
windowResize: () => { windowResize: () => {
this.calendar.changeView(this.currentView()); this.calendar.changeView(this.currentView());
this.calendar.setOption("headerToolbar", this.currentToolbar()); this.calendar.setOption("headerToolbar", this.currentHeaderToolbar());
this.calendar.setOption("footerToolbar", this.currentFooterToolbar());
}, },
eventClick: (event) => { eventClick: (event) => {
// Avoid our popup to be deleted because we clicked outside of it // Avoid our popup to be deleted because we clicked outside of it

View File

@ -98,4 +98,26 @@ ics-calendar {
background: white; background: white;
} }
} }
.fc .fc-toolbar.fc-footer-toolbar {
margin-bottom: 0.5em;
}
button.text-copy,
button.text-copy:focus,
button.text-copy:hover {
background-color: #67AE6E !important;
transition: 500ms ease-in;
}
button.text-copied,
button.text-copied:focus,
button.text-copied:hover {
transition: 500ms ease-out;
}
button.text-copied[tooltip]::before {
opacity: 0;
transition: opacity 500ms ease-out;
}
} }

View File

@ -51,24 +51,55 @@ body {
[tooltip]::before { [tooltip]::before {
@include shadow; @include shadow;
opacity: 0;
z-index: 1; z-index: 1;
pointer-events: none;
content: attr(tooltip); content: attr(tooltip);
background: hsl(219.6, 20.8%, 96%); left: 50%;
color: $black-color; transform: translateX(-50%);
background-color: #333;
color: #fff;
border: 0.5px solid hsl(0, 0%, 50%); border: 0.5px solid hsl(0, 0%, 50%);
;
border-radius: 5px; border-radius: 5px;
padding: 5px; padding: 5px 10px;
top: 1em;
position: absolute; position: absolute;
margin-top: 5px;
white-space: nowrap; white-space: nowrap;
opacity: 0;
transition: opacity 500ms ease-out; transition: opacity 500ms ease-out;
top: 120%; // Put the tooltip under the element
} }
[tooltip]:hover::before { [tooltip]:hover::before {
opacity: 1; opacity: 1;
transition: opacity 500ms ease-in;
}
[no-hover][tooltip]::before {
opacity: 1;
transition: opacity 500ms ease-in;
}
[position="top"][tooltip]::before {
top: initial;
bottom: 120%;
}
[position="bottom"][tooltip]::before {
top: 120%;
bottom: initial;
}
[position="left"][tooltip]::before {
top: initial;
bottom: 0%;
left: initial;
right: 65%;
}
[position="right"][tooltip]::before {
top: initial;
bottom: 0%;
left: 150%;
right: initial;
} }
.ib { .ib {

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-06 15:47+0200\n" "POT-Creation-Date: 2025-04-08 11:42+0200\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n" "Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -33,6 +33,14 @@ msgstr "Dépublier"
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
#: com/static/bundled/com/components/ics-calendar-index.ts
msgid "Copy calendar link"
msgstr "Copier le lien du calendrier"
#: com/static/bundled/com/components/ics-calendar-index.ts
msgid "Link copied"
msgstr "Lien copié"
#: com/static/bundled/com/components/moderation-alert-index.ts #: com/static/bundled/com/components/moderation-alert-index.ts
#, javascript-format #, javascript-format
msgid "" msgid ""

View File

@ -78,10 +78,12 @@ DEBUG = env.bool("SITH_DEBUG", default=False)
TESTING = "pytest" in sys.modules TESTING = "pytest" in sys.modules
INTERNAL_IPS = ["127.0.0.1"] INTERNAL_IPS = ["127.0.0.1"]
HTTPS = env.bool("HTTPS", default=True)
# force csrf tokens and cookies to be secure when in https # force csrf tokens and cookies to be secure when in https
CSRF_COOKIE_SECURE = env.bool("HTTPS", default=True) CSRF_COOKIE_SECURE = HTTPS
CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[]) CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[])
SESSION_COOKIE_SECURE = env.bool("HTTPS", default=True) SESSION_COOKIE_SECURE = HTTPS
X_FRAME_OPTIONS = "SAMEORIGIN" X_FRAME_OPTIONS = "SAMEORIGIN"
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]