From cbe9887efb4a9fab5f07490a0a0533ca798bfdb2 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 23 Sep 2025 18:39:01 +0200 Subject: [PATCH 1/8] Create unified notification system --- core/static/bundled/utils/notifications.ts | 24 +++++++++ core/static/core/header.scss | 1 - core/static/core/js/script.js | 38 -------------- core/templates/core/base.jinja | 9 ++-- core/templates/core/base/header.jinja | 8 +-- core/templates/core/base/notifications.jinja | 24 +++++++++ .../eboutic/eboutic_billing_info.jinja | 8 --- eboutic/templates/eboutic/eboutic_main.jinja | 8 --- .../eboutic/eboutic_payment_result.jinja | 8 --- pedagogy/templates/pedagogy/uv_edit.jinja | 50 ------------------- 10 files changed, 55 insertions(+), 123 deletions(-) create mode 100644 core/static/bundled/utils/notifications.ts delete mode 100644 core/static/core/js/script.js create mode 100644 core/templates/core/base/notifications.jinja diff --git a/core/static/bundled/utils/notifications.ts b/core/static/bundled/utils/notifications.ts new file mode 100644 index 00000000..9d4426d2 --- /dev/null +++ b/core/static/bundled/utils/notifications.ts @@ -0,0 +1,24 @@ +export enum NotificationLevel { + Error = "error", + Warning = "warning", +} + +export function createNotification(message: string, level: NotificationLevel) { + const element = document.getElementById("notifications"); + if (element === null) { + return false; + } + return element.dispatchEvent( + new CustomEvent("notification-add", { + detail: { text: message, tag: level }, + }), + ); +} + +export function deleteNotifications() { + const element = document.getElementById("notifications"); + if (element === null) { + return false; + } + return element.dispatchEvent(new CustomEvent("notification-delete")); +} diff --git a/core/static/core/header.scss b/core/static/core/header.scss index 348d3796..7eca52f9 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -321,7 +321,6 @@ $hovered-red-text-color: #ff4d4d; >#header_notif { box-sizing: border-box; - display: none; position: absolute; margin: 0; background-color: whitesmoke; diff --git a/core/static/core/js/script.js b/core/static/core/js/script.js deleted file mode 100644 index 9be232dd..00000000 --- a/core/static/core/js/script.js +++ /dev/null @@ -1,38 +0,0 @@ -$(() => { - $("#quick_notif li").click(function () { - $(this).hide(); - }); -}); - -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -function createQuickNotif(msg) { - const el = document.createElement("li"); - el.textContent = msg; - el.addEventListener("click", () => el.parentNode.removeChild(el)); - document.getElementById("quick_notif").appendChild(el); -} - -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -function deleteQuickNotifs() { - const el = document.getElementById("quick_notif"); - while (el.firstChild) { - el.removeChild(el.firstChild); - } -} - -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -function displayNotif() { - $("#header_notif").toggle().parent().toggleClass("white"); -} - -// You can't get the csrf token from the template in a widget -// We get it from a cookie as a workaround, see this link -// https://docs.djangoproject.com/en/2.0/ref/csrf/#ajax -// Sadly, getting the cookie is not possible with CSRF_COOKIE_HTTPONLY or CSRF_USE_SESSIONS is True -// So, the true workaround is to get the token from the dom -// https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true -// biome-ignore lint/style/useNamingConvention: can't find it used anywhere but I will not play with the devil -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -function getCSRFToken() { - return $("[name=csrfmiddlewaretoken]").val(); -} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 5fb6fd5f..03db58d8 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -34,7 +34,6 @@ - {% block additional_css %}{% endblock %} {% block additional_js %}{% endblock %} @@ -74,11 +73,9 @@
- + {% block notifications %} + {% include "core/base/notifications.jinja" %} + {% endblock %}
{%- block tabs -%} diff --git a/core/templates/core/base/header.jinja b/core/templates/core/base/header.jinja index 4454aedb..59fb6035 100644 --- a/core/templates/core/base/header.jinja +++ b/core/templates/core/base/header.jinja @@ -74,9 +74,9 @@ {% endif %} >
-
- - +
+ + {% set notification_count = user.notifications.filter(viewed=False).count() %} {% if notification_count > 0 %} @@ -89,7 +89,7 @@ {% endif %} -
+
    {% if user.notifications.filter(viewed=False).count() > 0 %} {% for n in user.notifications.filter(viewed=False).order_by('-date') %} diff --git a/core/templates/core/base/notifications.jinja b/core/templates/core/base/notifications.jinja new file mode 100644 index 00000000..44673488 --- /dev/null +++ b/core/templates/core/base/notifications.jinja @@ -0,0 +1,24 @@ +
    + +
    diff --git a/eboutic/templates/eboutic/eboutic_billing_info.jinja b/eboutic/templates/eboutic/eboutic_billing_info.jinja index 8a5c8a83..429240b8 100644 --- a/eboutic/templates/eboutic/eboutic_billing_info.jinja +++ b/eboutic/templates/eboutic/eboutic_billing_info.jinja @@ -31,12 +31,4 @@

- - {% if messages %} - {% for message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %}
diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 1809d0b7..3ee0d745 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -22,14 +22,6 @@ {% block content %}

{% trans %}Eboutic{% endtrans %}

- {% if messages %} - {% for message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} -

Panier

diff --git a/eboutic/templates/eboutic/eboutic_payment_result.jinja b/eboutic/templates/eboutic/eboutic_payment_result.jinja index 719ebc58..6c03754d 100644 --- a/eboutic/templates/eboutic/eboutic_payment_result.jinja +++ b/eboutic/templates/eboutic/eboutic_payment_result.jinja @@ -4,14 +4,6 @@

{% trans %}Eboutic{% endtrans %}

- {% if messages %} - {% for message in messages %} -
- {{ message }} -
- {% endfor %} - {% endif %} - {% if success %} {% trans %}Payment successful{% endtrans %} {% else %} diff --git a/pedagogy/templates/pedagogy/uv_edit.jinja b/pedagogy/templates/pedagogy/uv_edit.jinja index b4869e14..c0709419 100644 --- a/pedagogy/templates/pedagogy/uv_edit.jinja +++ b/pedagogy/templates/pedagogy/uv_edit.jinja @@ -21,11 +21,6 @@ {{ field.errors }} {{ field }} - - - {% if field.name == 'code' %} - - {% endif %}

{% endif %} @@ -36,48 +31,3 @@

{% endblock %} - -{% block script %} - {{ super() }} - - -{% endblock %} From c6e86841b3dbe5e047aabca6d7207efd81e19d92 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 23 Sep 2025 19:10:33 +0200 Subject: [PATCH 2/8] Remove jquery remeanants --- club/templates/club/club_sellings.jinja | 5 +++-- core/templates/core/base.jinja | 3 --- package-lock.json | 25 --------------------- package.json | 2 -- pedagogy/templates/pedagogy/uv_detail.jinja | 24 ++++++++------------ tsconfig.json | 2 +- vite.config.mts | 12 ---------- 7 files changed, 13 insertions(+), 60 deletions(-) diff --git a/club/templates/club/club_sellings.jinja b/club/templates/club/club_sellings.jinja index 4928e356..3733d0c8 100644 --- a/club/templates/club/club_sellings.jinja +++ b/club/templates/club/club_sellings.jinja @@ -83,9 +83,10 @@ TODO : rewrite the pagination used in this template an Alpine one {{ paginate(paginated_result, paginator, "formPagination(this)") }} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 03db58d8..9d9006f7 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -32,9 +32,6 @@ - - - {% block additional_css %}{% endblock %} {% block additional_js %}{% endblock %} {% endblock %} diff --git a/package-lock.json b/package-lock.json index 774d7396..74df5d10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "easymde": "^2.19.0", "glob": "^11.0.0", "htmx.org": "^2.0.3", - "jquery": "^3.7.1", "js-cookie": "^3.0.5", "lit-html": "^3.3.0", "native-file-system-adapter": "^3.0.1", @@ -47,7 +46,6 @@ "@types/alpinejs": "^3.13.10", "@types/cytoscape-cxtmenu": "^3.4.4", "@types/cytoscape-klay": "^3.1.4", - "@types/jquery": "^3.5.31", "@types/js-cookie": "^3.0.6", "typescript": "^5.8.3", "vite": "^6.3.6", @@ -2889,16 +2887,6 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, - "node_modules/@types/jquery": { - "version": "3.5.33", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.33.tgz", - "integrity": "sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sizzle": "*" - } - }, "node_modules/@types/js-cookie": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", @@ -2919,13 +2907,6 @@ "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", "license": "MIT" }, - "node_modules/@types/sizzle": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", - "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/tern": { "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", @@ -4384,12 +4365,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" - }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", diff --git a/package.json b/package.json index 33363697..fedc2f8e 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "@types/alpinejs": "^3.13.10", "@types/cytoscape-cxtmenu": "^3.4.4", "@types/cytoscape-klay": "^3.1.4", - "@types/jquery": "^3.5.31", "@types/js-cookie": "^3.0.6", "typescript": "^5.8.3", "vite": "^6.3.6", @@ -61,7 +60,6 @@ "easymde": "^2.19.0", "glob": "^11.0.0", "htmx.org": "^2.0.3", - "jquery": "^3.7.1", "js-cookie": "^3.0.5", "lit-html": "^3.3.0", "native-file-system-adapter": "^3.0.1", diff --git a/pedagogy/templates/pedagogy/uv_detail.jinja b/pedagogy/templates/pedagogy/uv_detail.jinja index 9b164583..4e4d315f 100644 --- a/pedagogy/templates/pedagogy/uv_detail.jinja +++ b/pedagogy/templates/pedagogy/uv_detail.jinja @@ -13,16 +13,15 @@ {% block content %}
-

{% trans %}Back{% endtrans %}

- +

{{ object.code }} - {{ object.title }}


@@ -217,9 +216,4 @@
- - {% endblock %} diff --git a/tsconfig.json b/tsconfig.json index 59a3642f..25b3cd17 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "resolveJsonModule": true, - "types": ["jquery", "alpinejs"], + "types": ["alpinejs"], "paths": { "#openapi": ["./staticfiles/generated/openapi/client/index.ts"], "#openapi:*": ["./staticfiles/generated/openapi/client/*"], diff --git a/vite.config.mts b/vite.config.mts index 604d9d78..d191d017 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -4,7 +4,6 @@ import inject from "@rollup/plugin-inject"; import { glob } from "glob"; import { type AliasOptions, type UserConfig, defineConfig } from "vite"; import type { Rollup } from "vite"; -import { viteStaticCopy } from "vite-plugin-static-copy"; import tsconfig from "./tsconfig.json"; const outDir = resolve(__dirname, "./staticfiles/generated/bundled"); @@ -87,17 +86,6 @@ export default defineConfig((config: UserConfig) => { Alpine: "alpinejs", htmx: "htmx.org", }), - viteStaticCopy({ - targets: [ - { - src: resolve(nodeModules, "jquery/dist/jquery.min.js"), - dest: vendored, - }, - ], - }), ], - optimizeDeps: { - include: ["jquery"], - }, } satisfies UserConfig; }); From 7eaf25a64f7c232aa4612505f2d8c681a1b02eb3 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 23 Sep 2025 20:26:23 +0200 Subject: [PATCH 3/8] Remove QuikNotifMixin --- com/templates/com/weekmail.jinja | 4 +- com/views.py | 41 ++++---- core/static/bundled/utils/notifications.ts | 9 +- core/static/core/style.scss | 11 --- core/templates/core/base/notifications.jinja | 6 +- core/views/mixins.py | 31 ------- core/views/user.py | 4 +- docs/reference/core/mixins.md | 1 - locale/fr/LC_MESSAGES/django.po | 98 ++++++++++++-------- sith/settings.py | 8 -- trombi/views.py | 49 +++++----- 11 files changed, 116 insertions(+), 146 deletions(-) diff --git a/com/templates/com/weekmail.jinja b/com/templates/com/weekmail.jinja index f8b37cbf..1342d9b0 100644 --- a/com/templates/com/weekmail.jinja +++ b/com/templates/com/weekmail.jinja @@ -31,9 +31,7 @@ {% trans %}Edit{% endtrans %} | {% trans %}Delete{% endtrans %} | - {% trans %}Add to weekmail{% endtrans %} | - {% trans %}Up{% endtrans %} | - {% trans %}Down{% endtrans %} + {% trans %}Add to weekmail{% endtrans %} {% endfor %} diff --git a/com/views.py b/com/views.py index 1b58feac..a1897b12 100644 --- a/com/views.py +++ b/com/views.py @@ -28,6 +28,7 @@ from typing import Any from dateutil.relativedelta import relativedelta from django.conf import settings +from django.contrib import messages from django.contrib.auth.mixins import ( PermissionRequiredMixin, ) @@ -55,7 +56,7 @@ from core.auth.mixins import ( PermissionOrClubBoardRequiredMixin, ) from core.models import User -from core.views.mixins import QuickNotifMixin, TabedViewMixin +from core.views.mixins import TabedViewMixin from core.views.widgets.markdown import MarkdownInput # Sith object @@ -333,7 +334,7 @@ class NewsFeed(Feed): # Weekmail -class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, DetailView): +class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView): model = Weekmail template_name = "com/weekmail_preview.jinja" success_url = reverse_lazy("com:weekmail") @@ -345,12 +346,11 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai def post(self, request, *args, **kwargs): self.object = self.get_object() + messages.success(self.request, _("Weekmail sent successfully")) if request.POST["send"] == "validate": try: self.object.send() - return HttpResponseRedirect( - reverse("com:weekmail") + "?qn_weekmail_send_success" - ) + return HttpResponseRedirect(reverse("com:weekmail")) except SMTPRecipientsRefused as e: self.bad_recipients = e.recipients elif request.POST["send"] == "clean": @@ -361,7 +361,6 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai for u in users: u.preferences.receive_weekmail = False u.preferences.save() - self.quick_notif_list += ["qn_success"] return super().get(request, *args, **kwargs) def get_object(self, queryset=None): @@ -375,7 +374,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai return kwargs -class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): +class WeekmailEditView(ComTabsMixin, CanEditPropMixin, UpdateView): model = Weekmail template_name = "com/weekmail.jinja" form_class = modelform_factory( @@ -415,7 +414,10 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi art.rank, prev_art.rank = prev_art.rank, art.rank art.save() prev_art.save() - self.quick_notif_list += ["qn_success"] + messages.success( + self.request, + _("%(title)s moved up in the Weekmail") % {"title": art.title}, + ) if "down_article" in request.GET: art = get_object_or_404( WeekmailArticle, id=request.GET["down_article"], weekmail=self.object @@ -427,7 +429,10 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi art.rank, next_art.rank = next_art.rank, art.rank art.save() next_art.save() - self.quick_notif_list += ["qn_success"] + messages.success( + self.request, + _("%(title)s moved down in the Weekmail") % {"title": art.title}, + ) if "add_article" in request.GET: art = get_object_or_404( WeekmailArticle, id=request.GET["add_article"], weekmail=None @@ -436,7 +441,10 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi art.rank = self.object.articles.aggregate(Max("rank"))["rank__max"] or 0 art.rank += 1 art.save() - self.quick_notif_list += ["qn_success"] + messages.success( + self.request, + _("%(title)s added to the Weekmail") % {"title": art.title}, + ) if "del_article" in request.GET: art = get_object_or_404( WeekmailArticle, id=request.GET["del_article"], weekmail=self.object @@ -444,7 +452,10 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi art.weekmail = None art.rank = -1 art.save() - self.quick_notif_list += ["qn_success"] + messages.success( + self.request, + _("%(title)s removed from the Weekmail") % {"title": art.title}, + ) return super().get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -454,9 +465,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi return kwargs -class WeekmailArticleEditView( - ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView -): +class WeekmailArticleEditView(ComTabsMixin, CanEditPropMixin, UpdateView): """Edit an article.""" model = WeekmailArticle @@ -468,11 +477,10 @@ class WeekmailArticleEditView( pk_url_kwarg = "article_id" template_name = "core/edit.jinja" success_url = reverse_lazy("com:weekmail") - quick_notif_url_arg = "qn_weekmail_article_edit" current_tab = "weekmail" -class WeekmailArticleCreateView(QuickNotifMixin, CreateView): +class WeekmailArticleCreateView(CreateView): """Post an article.""" model = WeekmailArticle @@ -483,7 +491,6 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): ) template_name = "core/create.jinja" success_url = reverse_lazy("core:user_tools") - quick_notif_url_arg = "qn_weekmail_new_article" def get_initial(self): if "club" not in self.request.GET: diff --git a/core/static/bundled/utils/notifications.ts b/core/static/bundled/utils/notifications.ts index 9d4426d2..70e5fd91 100644 --- a/core/static/bundled/utils/notifications.ts +++ b/core/static/bundled/utils/notifications.ts @@ -1,24 +1,25 @@ export enum NotificationLevel { Error = "error", Warning = "warning", + Success = "success", } export function createNotification(message: string, level: NotificationLevel) { - const element = document.getElementById("notifications"); + const element = document.getElementById("quick-notifications"); if (element === null) { return false; } return element.dispatchEvent( - new CustomEvent("notification-add", { + new CustomEvent("quick-notification-add", { detail: { text: message, tag: level }, }), ); } export function deleteNotifications() { - const element = document.getElementById("notifications"); + const element = document.getElementById("quick-notifications"); if (element === null) { return false; } - return element.dispatchEvent(new CustomEvent("notification-delete")); + return element.dispatchEvent(new CustomEvent("quick-notification-delete")); } diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 2baf42a6..771ca5e2 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -270,17 +270,6 @@ body { } /*--------------------------------CONTENT------------------------------*/ - #quick_notif { - width: 100%; - margin: 0 auto; - list-style-type: none; - background: $second-color; - - li { - padding: 10px; - } - } - #content { padding: 1em 1%; box-shadow: $shadow-color 0 5px 10px; diff --git a/core/templates/core/base/notifications.jinja b/core/templates/core/base/notifications.jinja index 44673488..b50ec89f 100644 --- a/core/templates/core/base/notifications.jinja +++ b/core/templates/core/base/notifications.jinja @@ -1,4 +1,4 @@ -
+ @quick-notification-add="(e) => messages.push(e?.detail)" + @quick-notification-delete="messages = []">