Author SHA1 Message Date
klmp200andGitHub ebfac638de Merge pull request #1465 from ae-utbm/hotfix
Fix broken product widget
2026-08-30 22:40:08 +02:00
klmp200andGitHub 839536661b Merge pull request #1466 from ae-utbm/htmx4
Migrate to HTMX4
2026-08-30 22:39:52 +02:00
thomas girodandGitHub b1b3639dc9 Merge pull request #1467 from ae-utbm/pedagogy-style
Pedagogy style
2026-08-30 22:38:34 +02:00
klmp200 56c832ba06 Migrate to HTMX4 2026-08-30 22:07:53 +02:00
imperosol 884019d803 style: standardize UE search style 2026-08-30 21:53:00 +02:00
klmp200 dfc714ff3d Fix broken ProductAjaxSelect widget 2026-08-28 15:05:17 +02:00
13 changed files with 84 additions and 139 deletions
@@ -7,7 +7,7 @@
<form
hx-post="{{ url('club:club_new_members', club_id=club.id) }}"
hx-disabled-elt="find input[type='submit']"
hx-disable="find input[type='submit']"
hx-swap="outerHTML"
hx-target="#member-fragment-container"
id="add_club_members_form"
+10 -5
View File
@@ -6,10 +6,15 @@
* for more efficient tree-shaking and gzip compression.
*/
// Must be loaded before Apline
import htmx from "htmx.org";
import "htmx.org/dist/ext/hx-alpine-compat.js";
import "htmx.org/dist/ext/hx-prompt.js";
import "htmx.org/dist/ext/hx-download.js";
import sort from "@alpinejs/sort";
import Alpine from "alpinejs";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
import htmx from "htmx.org";
import { limitedChoices } from "#core:alpine/limited-choices";
import { expireOldStorage } from "#core:core/localstorage";
import { default as navbar } from "#core:core/navbar";
@@ -44,16 +49,16 @@ polyfillCountryFlagEmojis();
* HTMX
*/
document.body.addEventListener(
"htmx:beforeRequest" as keyof HTMLElementEventMap,
"htmx:before:request" as keyof HTMLElementEventMap,
(event) => {
(event as CustomEvent).detail.target.ariaBusy = true;
(event as CustomEvent).detail.ctx.target.ariaBusy = true;
},
);
document.body.addEventListener(
"htmx:beforeSwap" as keyof HTMLElementEventMap,
"htmx:before:swap" as keyof HTMLElementEventMap,
(event) => {
(event as CustomEvent).detail.target.ariaBusy = null;
(event as CustomEvent).detail.ctx.target.ariaBusy = null;
},
);
@@ -1,6 +1,6 @@
<form
hx-post="{{ url("core:user_visibility_fragment", user_id=form.instance.id) }}"
hx-disabled-elt="find input[type='submit']"
hx-disable="find input[type='submit']"
hx-swap="outerHTML" x-data="{ isViewable: {{ form.is_viewable.value()|tojson }} }"
>
{% for message in messages %}
@@ -28,18 +28,23 @@ export class ProductAjaxSelect extends AjaxSelect {
return [];
}
private getName(item: SimpleProductSchema, sanitize: typeof escape_html): string {
// In the context in which this method is called, `this` might be shadowed
// We need to call it explicitly from the class itself
private static getName(
item: SimpleProductSchema,
sanitize: typeof escape_html,
): string {
return item.code ? `${sanitize(item.code)} - ${sanitize(item.name)}` : item.name;
}
protected renderOption(item: SimpleProductSchema, sanitize: typeof escape_html) {
return `<div class="select-item">
<span class="select-item-text">${this.getName(item, sanitize)}</span>
<span class="select-item-text">${ProductAjaxSelect.getName(item, sanitize)}</span>
</div>`;
}
protected renderItem(item: SimpleProductSchema, sanitize: typeof escape_html) {
return `<span>${this.getName(item, sanitize)}</span>`;
return `<span>${ProductAjaxSelect.getName(item, sanitize)}</span>`;
}
}
@@ -1,5 +1,10 @@
<div id="student_card_form">
<form hx-post="{{ action }}" hx-swap="outerHTML" hx-target="#student_card_form">
<form
hx-post="{{ action }}"
hx-swap="outerHTML"
hx-target="#student_card_form"
hx-disable="input[type='submit']"
>
{% csrf_token %}
<p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p>
<input type="submit" value="{% trans %}Confirm{% endtrans %}" />
+1 -1
View File
@@ -217,7 +217,7 @@ C'est une technologie simple et puissante qui se veut comme le jQuery du web mod
### Htmx
[Site officiel](https://htmx.org/)
[Site officiel](https://four.htmx.org/)
En plus de AlpineJS, linteractivité sur le site est augmentée via Htmx.
C'est une librairie js qui s'utilise également au moyen d'attributs HTML à
@@ -41,7 +41,7 @@
hx-post="{{ url("election:apply_result", election_id=form.election.id) }}"
hx-swap="outerHTML"
hx-target="#apply-election-result-fragment"
hx-disabled-elt="find input[type='submit']"
hx-disable="find input[type='submit']"
>
{% csrf_token %}
{{ form }}
+11 -5
View File
@@ -30,7 +30,7 @@
"easymde": "^2.21.0",
"glob": "^13.0.6",
"html2canvas": "^1.4.1",
"htmx.org": "^2.0.10",
"htmx.org": "^4.0.0",
"js-cookie": "^3.0.8",
"lit-html": "^3.3.3",
"native-file-system-adapter": "^3.0.1",
@@ -3801,10 +3801,16 @@
}
},
"node_modules/htmx.org": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.10.tgz",
"integrity": "sha512-kdeJe7ZVwaS6QMz/ebBIVtZdpwen6L0OQ5GOhPV9MKBb196TCZeZu4yA7ZIQsaLKv7EpXz+So7KSXNuHXhj7Cw==",
"license": "0BSD"
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-4.0.0.tgz",
"integrity": "sha512-T/171FUY93Kdfp8t+DnHdk45QvKRiBhVhhrwSzrXgUi4pHKvhp77dUA/qg8FAjsFWPIHNbmUuIdCrcVHuiZWng==",
"license": "BSD-0-Clause",
"workspaces": [
"ext/*"
],
"bin": {
"upgrade-check": "dist/scripts/upgrade-check.js"
}
},
"node_modules/ical.js": {
"version": "1.5.0",
+2 -2
View File
@@ -34,8 +34,8 @@
"@types/cytoscape-klay": "^3.1.5",
"@types/js-cookie": "^3.0.6",
"@types/node": "^26.2.0",
"rollup-plugin-visualizer": "^7.1.1",
"@typescript/native": "npm:typescript@^7.0.2",
"rollup-plugin-visualizer": "^7.1.1",
"typescript": "npm:@typescript/typescript6@^6.0.2",
"vite": "^8.2.2"
},
@@ -61,7 +61,7 @@
"easymde": "^2.21.0",
"glob": "^13.0.6",
"html2canvas": "^1.4.1",
"htmx.org": "^2.0.10",
"htmx.org": "^4.0.0",
"js-cookie": "^3.0.8",
"lit-html": "^3.3.3",
"native-file-system-adapter": "^3.0.1",
+11 -88
View File
@@ -64,100 +64,23 @@ $pedagogy-white-text: #f0f0f0;
}
#search_form {
.search-form-container {
display: grid;
grid-template-columns: auto auto;
grid-template-rows: auto auto auto;
grid-template-areas:
"action-bar action-bar"
"search-bar search-bar"
"radio-department radio-department"
"radio-credit-type radio-semester";
}
.action-bar {
grid-area: action-bar;
margin-bottom: 10px;
}
.search-bar {
grid-area: search-bar;
display: grid;
grid-template-columns: auto 200px;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
@media screen and (max-width: $medium-devices) {
grid-template-columns: auto auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input search-bar-button";
.radio-guide fieldset {
input[type="checkbox"] {
display: none;
}
@media screen and (max-width: $small-devices) {
grid-template-columns: auto;
grid-template-rows: auto;
grid-template-areas: "search-bar-input";
.search-bar-button {
display: none;
}
}
.search-bar-input {
grid-area: search-bar-input;
background: $pedagogy-light-blue;
}
.search-bar-button {
grid-area: search-bar-button;
background: $pedagogy-orange;
color: white;
font-weight: bold;
margin-left: 20px;
}
}
.radio-department {
grid-area: radio-department;
}
.radio-credit-type {
grid-area: radio-credit-type;
}
.radio-semester {
grid-area: radio-semester;
}
.radio-guide input[type="radio"],
input[type="checkbox"] {
display: none;
}
.radio-guide {
margin-top: 10px;
margin-bottom: 0;
color: white;
}
.radio-guide label {
display: inline-block;
background-color: $pedagogy-blue;
padding: 10px 20px;
font-family: Arial, sans-serif;
font-size: 16px;
border-radius: 4px;
}
label {
padding: 10px 20px;
}
.radio-guide input[type="radio"]:checked+label {
background-color: $pedagogy-orange;
}
.radio-guide input[type="checkbox"]:checked+label {
background-color: $pedagogy-orange;
}
.radio-guide label:hover {
background-color: $pedagogy-hover-blue;
input[type="checkbox"]:checked+label {
background-color: $pedagogy-orange;
@include shadow;
}
}
}
+30 -29
View File
@@ -14,7 +14,7 @@
{% endblock %}
{% block additional_js %}
<script type="module" src="{{ static('bundled/pedagogy/guide-index.js') }}"></script>
<script type="module" src="{{ static('bundled/pedagogy/guide-index.ts') }}"></script>
{% endblock %}
{% block head %}
@@ -24,30 +24,28 @@
{% block content %}
{% if user.has_perm("pedagogy.add_ue") %}
<div class="action-bar">
<p>
<a href="{{ url('pedagogy:ue_create') }}">{% trans %}Create UE{% endtrans %}</a>
</p>
<p>
<a href="{{ url('pedagogy:moderation') }}">{% trans %}Moderate comments{% endtrans %}</a>
</p>
<div class="row gap">
<a href="{{ url('pedagogy:ue_create') }}" class="btn btn-blue"><i class="fa fa-plus"></i>{% trans %}Create UE{% endtrans %}</a>
<a href="{{ url('pedagogy:moderation') }}" class="btn btn-grey">{% trans %}Moderate comments{% endtrans %}</a>
</div>
<br/>
{% endif %}
<div class="pedagogy" x-data="ue_search" x-cloak>
<form id="search_form">
<form id="search_form" class="">
<div class="search-form-container">
<div class="search-bar">
<fieldset>
<label for="search_input">{% trans %}Search UE{% endtrans %}</label>
<input
id="search_input"
class="search-bar-input"
type="text"
type="search"
name="search"
placeholder=""
x-model.debounce.500ms="search"
/>
</div>
<div class="radio-department">
<div class="radio-guide">
</fieldset>
<div class="row gap-3x margin-bottom radio-guide">
<fieldset>
{% set departments = [
("EDIM", "EDIM"), ("ENERGIE", "EE"), ("IMSI", "IMSI"),
("INFO", "GI"), ("GMC", "MC"), ("HUMA", "HUMA"), ("TC", "TC")
@@ -56,16 +54,16 @@
<input
type="checkbox"
name="department"
id="radio{{ real_name }}"
id="radio_{{ real_name }}"
value="{{ real_name }}"
x-model="department"
/>
<label for="radio{{ real_name }}">{% trans %}{{ display_name }}{% endtrans %}</label>
<label for="radio_{{ real_name }}" class="btn btn-blue">
{% trans %}{{ display_name }}{% endtrans %}
</label>
{% endfor %}
</div>
</div>
<div class="radio-credit-type">
<div class="radio-guide">
</fieldset>
<fieldset>
{% for credit_type in ["CS", "TM", "EC", "QC", "OM"] %}
<input
type="checkbox"
@@ -74,18 +72,21 @@
value="{{ credit_type }}"
x-model="credit_type"
/>
<label for="radio{{ credit_type }}">{% trans %}{{ credit_type }}{% endtrans %}</label>
<label for="radio{{ credit_type }}" class="btn btn-blue">
{% trans %}{{ credit_type }}{% endtrans %}
</label>
{% endfor %}
</div>
</div>
<div class="radio-semester">
<div class="radio-guide">
</fieldset>
<fieldset>
<input type="checkbox" name="semester" id="radioAUTUMN" value="AUTUMN" x-model="semester"/>
<label for="radioAUTUMN"><i class="fa fa-leaf"></i></label>
<label for="radioAUTUMN" class="btn btn-no-text btn-blue">
<i class="fa fa-leaf"></i>
</label>
<input type="checkbox" name="semester" id="radioSPRING" value="SPRING" x-model="semester"/>
<label for="radioSPRING"><i class="fa-regular fa-sun"></i></label>
</div>
<label for="radioSPRING" class="btn btn-no-text btn-blue">
<i class="fa-regular fa-sun"></i>
</label>
</fieldset>
</div>
</div>
</form>
@@ -1,7 +1,7 @@
<form
hx-post="{{ url("subscription:fragment-existing-user") }}"
hx-target="this"
hx-disabled-elt="find input[type='submit']"
hx-disable="find input[type='submit']"
hx-swap="outerHTML"
>
{% csrf_token %}
@@ -1,7 +1,7 @@
<form
hx-post="{{ url("subscription:fragment-new-user") }}"
hx-target="this"
hx-disabled-elt="find input[type='submit']"
hx-disable="find input[type='submit']"
hx-swap="outerHTML"
>
{% csrf_token %}