From 2832e76a3152dcbfd96870af18f20843dad85114 Mon Sep 17 00:00:00 2001 From: Sli Date: Thu, 27 Aug 2026 00:27:50 +0200 Subject: [PATCH] Htmx callback plugin and htmx based report view --- core/static/bundled/base-bundle-index.ts | 4 + core/static/bundled/htmx/error-callback.ts | 74 +++++++++++++++++++ pedagogy/static/pedagogy/css/pedagogy.scss | 11 ++- .../pedagogy/fragments/comment_report.jinja | 30 ++++++++ .../pedagogy/fragments/ue_comment.jinja | 8 +- pedagogy/templates/pedagogy/macros.jinja | 2 +- pedagogy/views.py | 28 +++++-- 7 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 core/static/bundled/htmx/error-callback.ts create mode 100644 pedagogy/templates/pedagogy/fragments/comment_report.jinja diff --git a/core/static/bundled/base-bundle-index.ts b/core/static/bundled/base-bundle-index.ts index 8556a99e..eae4ce75 100644 --- a/core/static/bundled/base-bundle-index.ts +++ b/core/static/bundled/base-bundle-index.ts @@ -18,6 +18,7 @@ import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; import { limitedChoices } from "#core:alpine/limited-choices"; import { expireOldStorage } from "#core:core/localstorage"; import { default as navbar } from "#core:core/navbar"; +import { getErrorCallbacksExt } from "#core:htmx/error-callback"; import { type NotificationPlugin, notificationsPlugin as notifications, @@ -62,6 +63,9 @@ document.body.addEventListener( }, ); +const errorCallbackExt = getErrorCallbacksExt(); +htmx.registerExtension(errorCallbackExt.name, errorCallbackExt.extension); + Object.assign(window, { htmx }); /** diff --git a/core/static/bundled/htmx/error-callback.ts b/core/static/bundled/htmx/error-callback.ts new file mode 100644 index 00000000..276181d4 --- /dev/null +++ b/core/static/bundled/htmx/error-callback.ts @@ -0,0 +1,74 @@ +interface CustomHtmxExtension { + name: string; + extension: any; +} + +export const getErrorCallbacksExt = () => { + const attrPrefix = "hx-callback-"; + let htmxApi: { attributeValue: (arg0: HTMLElement, arg1: string) => string | null }; + + const getCallback = (elt: HTMLElement, responseCode: number) => { + if (!elt || !responseCode) { + return () => {}; + } + + const code = responseCode.toString(); + + // '*' is the original syntax, as the obvious character for a wildcard. + // The 'x' alternative was added for maximum compatibility with HTML + // templating engines, due to ambiguity around which characters are + // supported in HTML attributes. + // + // Start with the most specific possible attribute and generalize from + // there. + const suffixes = [ + code, + + `${code.substring(0, 2)}*`, + `${code.substring(0, 2)}x`, + + `${code.substring(0, 1)}*`, + `${code.substring(0, 1)}x`, + `${code.substring(0, 1)}**`, + `${code.substring(0, 1)}xx`, + + "*", + "x", + "***", + "xxx", + ]; + if (code.startsWith("4") || code.startsWith("5")) { + suffixes.push("error"); + } + + for (const suffix of suffixes) { + const attr = attrPrefix + suffix; + const callback = htmxApi?.attributeValue(elt, attr); + if (callback) { + return Function("src", "target", callback); + } + } + + return () => {}; + }; + + return { + name: "error-callbacks", + extension: { + init: (api: any) => { + htmxApi = api; + }, + // biome-ignore lint/style/useNamingConvention: HTMX naming convention + htmx_response_error: ( + elt: HTMLElement, + event: { ctx: any; cancelled: boolean }, + ) => { + if (event.cancelled) { + return false; + } + getCallback(elt, event.ctx.response.status)(elt, event.ctx.target); + return true; + }, + }, + } as CustomHtmxExtension; +}; diff --git a/pedagogy/static/pedagogy/css/pedagogy.scss b/pedagogy/static/pedagogy/css/pedagogy.scss index 178b7962..a177ee4e 100644 --- a/pedagogy/static/pedagogy/css/pedagogy.scss +++ b/pedagogy/static/pedagogy/css/pedagogy.scss @@ -168,12 +168,6 @@ $pedagogy-white-text: #f0f0f0; .input-stars { margin-top: 20px; } - - .right { - display: flex; - justify-content: flex-end; - } - } .ue-details-container { @@ -446,4 +440,9 @@ details.accordion>.accordion-content { background-color: $white-color; border-color: $pedagogy-orange; border-right: none; +} + +.right { + display: flex; + justify-content: flex-end; } \ No newline at end of file diff --git a/pedagogy/templates/pedagogy/fragments/comment_report.jinja b/pedagogy/templates/pedagogy/fragments/comment_report.jinja new file mode 100644 index 00000000..d729f545 --- /dev/null +++ b/pedagogy/templates/pedagogy/fragments/comment_report.jinja @@ -0,0 +1,30 @@ +
+ {% csrf_token %} + {{ form.non_field_errors() }} + {{ form.reason.errors }} + {{ form.reason }} + + {# Hidden fields #} + {{ form.reporter }} + {{ form.comment }} + + + +

+ +

+
\ No newline at end of file diff --git a/pedagogy/templates/pedagogy/fragments/ue_comment.jinja b/pedagogy/templates/pedagogy/fragments/ue_comment.jinja index 6361e8d9..3a4e5cd6 100644 --- a/pedagogy/templates/pedagogy/fragments/ue_comment.jinja +++ b/pedagogy/templates/pedagogy/fragments/ue_comment.jinja @@ -47,10 +47,12 @@ {% endif %} {% if comment.author_id == user.id or user.has_perm("pedagogy.delete_comment") %}
{% csrf_token %}