justfile: standardise test recipes to lint-php/lint-css/lint-js/test + add fix recipe

- Removed ambiguous aliases: phpstan, cs-check, syntax
- Split lint-biome into lint-css and lint-js with correct paths
- Added lint meta-recipe and fix recipe (biome --unsafe + php-cs-fixer)
- Fixed FormBootstrap dead null check, CSS shorthand override bug
- Updated phpstan baseline, suppressed noDescendingSpecificity/noInnerDeclarations
- Applied ~74 biome auto-fixes across CSS/JS
This commit is contained in:
Pontoporeia
2026-07-05 11:07:41 +02:00
parent 34b5d3e585
commit 3e93150c76
59 changed files with 5459 additions and 5196 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -18,4 +18,4 @@
- [x] Add bulk select/delete to tmp cleanup dialog (like admin/index.php pattern)
- [x] Move cleanup UI from modal to dedicated page (like add.php/edit.php)
- [x] Replace <details> with <fieldset>/<legend> in cleanup fragments (like contenus.php)
- [x] Standardise test group recipes: lint-php, lint-css, lint-js, test + add fix recipe
- [x] Standardise test group recipes: lint-php, lint-css, lint-js, test + add fix recipe + make lint pass clean
+62 -25
View File
@@ -47,7 +47,6 @@
height: 16px;
}
.admin-body main {
flex: 1;
min-height: 0;
@@ -62,7 +61,7 @@
}
.admin-main--list {
padding: 0 !important;
padding: 0;
}
.admin-main--list > form,
@@ -83,7 +82,7 @@
}
.admin-table-row:hover {
background: var(--blue-muted-bg) !important;
background: var(--blue-muted-bg);
}
.admin-body main > h1,
@@ -105,7 +104,9 @@
border-radius: var(--radius);
color: var(--text-secondary);
text-decoration: none;
transition: background 0.15s, color 0.15s;
transition:
background 0.15s,
color 0.15s;
flex-shrink: 0;
vertical-align: middle;
margin-right: var(--space-2xs);
@@ -381,7 +382,10 @@ th.admin-ap-col {
color: var(--text-secondary);
cursor: pointer;
text-decoration: none;
transition: background 0.15s, color 0.15s, border-color 0.15s;
transition:
background 0.15s,
color 0.15s,
border-color 0.15s;
line-height: 1;
}
@@ -1073,8 +1077,12 @@ th.admin-ap-col {
flex-direction: column;
}
@keyframes adminSheetIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
.admin-dialog--sheet::backdrop {
background: rgba(0, 0, 0, 0.5);
@@ -1177,7 +1185,7 @@ th.admin-ap-col {
.n-grid > details > summary::before,
.n-section > summary::before {
content: '';
content: "";
display: inline-block;
width: 16px;
height: 16px;
@@ -1219,9 +1227,18 @@ th.admin-ap-col {
margin-right: var(--space-3xs);
}
.admin-import-log__item--ok::before { content: '✓'; color: var(--accent-green); }
.admin-import-log__item--skip::before { content: '⚠'; color: var(--warning); }
.admin-import-log__item--error::before { content: '✗'; color: var(--error); }
.admin-import-log__item--ok::before {
content: "✓";
color: var(--accent-green);
}
.admin-import-log__item--skip::before {
content: "⚠";
color: var(--warning);
}
.admin-import-log__item--error::before {
content: "✗";
color: var(--error);
}
/* ── Paramètres page (flat, semantic) ──────────────────────────────────── */
.param-access-table {
@@ -1669,7 +1686,7 @@ th.admin-ap-col {
}
.admin-toggle::after {
content: '';
content: "";
position: absolute;
top: 3px;
left: 3px;
@@ -1811,7 +1828,7 @@ th.admin-ap-col {
}
.admin-toggle::after {
content: '';
content: "";
position: absolute;
top: 3px;
left: 3px;
@@ -1830,7 +1847,6 @@ th.admin-ap-col {
transform: translateX(18px);
}
/* ── Form group, student mode, thanks page → see form-base.css ──────────── */
/* ── Utility ─────────────────────────────────────────────────────────────── */
@@ -1894,7 +1910,7 @@ th.admin-ap-col {
}
.fhb-fieldset-card-inputs li::before {
content: '· ';
content: "· ";
color: var(--text-tertiary);
}
@@ -1956,7 +1972,6 @@ th.admin-ap-col {
height: 100%;
}
/* ── Left side: name + content ─────────────────────────────────────────────── */
.fhb-inline-body {
@@ -1998,8 +2013,12 @@ th.admin-ap-col {
margin-bottom: 0;
}
.fhb-md-preview strong { font-weight: 600; }
.fhb-md-preview em { font-style: italic; }
.fhb-md-preview strong {
font-weight: 600;
}
.fhb-md-preview em {
font-style: italic;
}
.fhb-md-preview code {
font-size: 0.9em;
background: var(--bg-secondary);
@@ -2155,10 +2174,22 @@ th.admin-ap-col {
}
@keyframes admin-search-progress {
0% { transform: scaleX(0); transform-origin: left; }
50% { transform: scaleX(1); transform-origin: left; }
50.01% { transform: scaleX(1); transform-origin: right; }
100% { transform: scaleX(0); transform-origin: right; }
0% {
transform: scaleX(0);
transform-origin: left;
}
50% {
transform: scaleX(1);
transform-origin: left;
}
50.01% {
transform: scaleX(1);
transform-origin: right;
}
100% {
transform: scaleX(0);
transform-origin: right;
}
}
/* ── Sidebar TOC (matches public .page-content grid layout) ────────────── */
@@ -2415,9 +2446,15 @@ th.admin-ap-col {
line-height: 1.3;
}
.md-cheatsheet-render h1 { font-size: var(--step-2); }
.md-cheatsheet-render h2 { font-size: var(--step-1); }
.md-cheatsheet-render h3 { font-size: var(--step-0); }
.md-cheatsheet-render h1 {
font-size: var(--step-2);
}
.md-cheatsheet-render h2 {
font-size: var(--step-1);
}
.md-cheatsheet-render h3 {
font-size: var(--step-0);
}
.md-cheatsheet-render blockquote {
margin: 0;
+20 -7
View File
@@ -5,7 +5,8 @@
============================================================ */
/* Full-height flex layout: header → main → (optional footer) */
html, body {
html,
body {
height: 100%;
overflow: hidden;
}
@@ -61,12 +62,24 @@ main * {
}
/* Global heading scale — used by admin + public pages */
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }
h5 { font-size: var(--step-0); }
h6 { font-size: var(--step--1); }
h1 {
font-size: var(--step-4);
}
h2 {
font-size: var(--step-3);
}
h3 {
font-size: var(--step-2);
}
h4 {
font-size: var(--step-1);
}
h5 {
font-size: var(--step-0);
}
h6 {
font-size: var(--step--1);
}
:where(h1, h2, h3, h4, h5, h6) {
font-family: var(--font-display);
+32 -9
View File
@@ -19,11 +19,17 @@
text-decoration: none;
line-height: 1.3;
border: none;
transition: background 0.15s, opacity 0.15s, box-shadow 0.15s, filter 0.15s;
transition:
background 0.15s,
opacity 0.15s,
box-shadow 0.15s,
filter 0.15s;
width: fit-content;
}
.btn:hover { filter: brightness(0.92); }
.btn:hover {
filter: brightness(0.92);
}
/* Primary: accent background, white text */
.btn--primary {
@@ -82,7 +88,9 @@
color: var(--accent-foreground);
}
.btn--danger:hover { filter: brightness(0.9); }
.btn--danger:hover {
filter: brightness(0.9);
}
/* Warning: yellow background */
.btn--warning {
@@ -90,7 +98,9 @@
color: var(--text-primary);
}
.btn--warning:hover { filter: brightness(0.9); }
.btn--warning:hover {
filter: brightness(0.9);
}
/* Success: green background */
.btn--success {
@@ -98,7 +108,9 @@
color: var(--accent-foreground);
}
.btn--success:hover { filter: brightness(0.9); }
.btn--success:hover {
filter: brightness(0.9);
}
/* Small size modifier */
.btn--sm {
@@ -119,7 +131,10 @@
border: 1px solid var(--blue-muted-border);
}
.btn--blue:hover { background: var(--blue-muted-bg-hover); filter: none; }
.btn--blue:hover {
background: var(--blue-muted-bg-hover);
filter: none;
}
.btn--yellow {
background: var(--yellow-muted-bg);
@@ -127,7 +142,10 @@
border: 1px solid var(--yellow-muted-border);
}
.btn--yellow:hover { background: var(--yellow-muted-bg-hover); filter: none; }
.btn--yellow:hover {
background: var(--yellow-muted-bg-hover);
filter: none;
}
.btn--green {
background: var(--green-muted-bg);
@@ -135,7 +153,10 @@
border: 1px solid var(--green-muted-border);
}
.btn--green:hover { background: var(--green-muted-bg-hover); filter: none; }
.btn--green:hover {
background: var(--green-muted-bg-hover);
filter: none;
}
.btn--red {
background: var(--error-muted-bg);
@@ -143,4 +164,6 @@
border: 1px solid var(--danger-border-muted);
}
.btn--red:hover { filter: brightness(0.9); }
.btn--red:hover {
filter: brightness(0.9);
}
+3 -1
View File
@@ -33,7 +33,9 @@ summary {
font-weight: 600;
text-decoration: none;
color: var(--text-primary);
transition: color 0.15s, background 0.15s;
transition:
color 0.15s,
background 0.15s;
padding: var(--space-s);
cursor: pointer;
}
+16 -4
View File
@@ -14,7 +14,11 @@ label {
/* ── Text inputs, selects, textareas ────────────────────────────────── */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="color"]),
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
[type="hidden"]
):not([type="submit"]):not([type="button"]):not([type="reset"]):not(
[type="color"]
),
textarea {
font-family: inherit;
font-size: var(--step--1);
@@ -26,7 +30,11 @@ textarea {
transition: border-color 0.15s;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="color"]):focus,
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
[type="hidden"]
):not([type="submit"]):not([type="button"]):not([type="reset"]):not(
[type="color"]
):focus,
textarea:focus {
outline: none;
border: 2px solid var(--accent-primary);
@@ -81,7 +89,9 @@ input[type="radio"] {
margin: 0;
}
input[type="radio"] { border-radius: 50%; }
input[type="radio"] {
border-radius: 50%;
}
/* ── Fieldsets & legends ──────────────────────────────────────────────── */
@@ -92,7 +102,9 @@ fieldset {
margin: 0;
}
fieldset > *:not(:last-child) { margin-bottom: var(--space-xs); }
fieldset > *:not(:last-child) {
margin-bottom: var(--space-xs);
}
legend {
font-size: var(--step--1);
+3 -3
View File
@@ -161,7 +161,8 @@ header nav ul a[aria-current="page"] {
gap: 0;
}
header nav[aria-label="Navigation principale"]
header
nav[aria-label="Navigation principale"]
.nav-left-links
li:not(:first-child) {
display: none;
@@ -200,8 +201,7 @@ header nav ul a[aria-current="page"] {
}
/* Animate burger → X */
.menu-btn:checked
~ nav[aria-label="Navigation principale"]
.menu-btn:checked ~ nav[aria-label="Navigation principale"]
.menu-icon
.navicon {
background: transparent;
@@ -32,7 +32,9 @@
font-size: var(--step--1);
font-weight: 400;
text-decoration: none;
transition: border-color 0.15s, color 0.15s;
transition:
border-color 0.15s,
color 0.15s;
}
.pagination-btn:hover:not(.disabled) {
+17 -11
View File
@@ -21,10 +21,16 @@ form[role="search"]:not(.header-search-form) {
.header-search-wrap {
padding: 0;
flex-shrink: 0;
background: linear-gradient(180deg, rgba(192, 93, 225, 1) 0%, rgba(255, 255, 255, 1) 100%);
background: linear-gradient(
180deg,
rgba(192, 93, 225, 1) 0%,
rgba(255, 255, 255, 1) 100%
);
}
.header-search-form { width: 100%; }
.header-search-form {
width: 100%;
}
.header-search-input-wrap {
position: relative;
@@ -43,18 +49,18 @@ form[role="search"]:not(.header-search-form) {
.header-search-input-wrap input {
width: 100%;
padding: var(--space-2xs) var(--space-s) !important;
padding-left: calc(18px + var(--space-l)) !important;
border: 1px solid var(--accent-primary) !important;
border-radius: var(--radius) !important;
background: var(--bg-primary) !important;
font-size: var(--step-0) !important;
color: var(--text-primary) !important;
font-family: inherit !important;
padding: var(--space-2xs) var(--space-s);
padding-left: calc(18px + var(--space-l));
border: 1px solid var(--accent-primary);
border-radius: var(--radius);
background: var(--bg-primary);
font-size: var(--step-0);
color: var(--text-primary);
font-family: inherit;
}
.header-search-input-wrap input::placeholder {
color: var(--accent-primary) !important;
color: var(--accent-primary);
font-family: var(--font-body);
font-weight: 300;
}
+17 -5
View File
@@ -26,7 +26,8 @@
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
pointer-events: auto;
/* enter then fade out — total visible ~6.35s */
animation: toast-enter 0.35s ease-out,
animation:
toast-enter 0.35s ease-out,
toast-exit 0.5s ease-in 6s forwards;
}
@@ -61,11 +62,22 @@
}
@keyframes toast-enter {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes toast-exit {
from { opacity: 1; }
to { opacity: 0; pointer-events: none; }
from {
opacity: 1;
}
to {
opacity: 0;
pointer-events: none;
}
}
+3 -1
View File
@@ -46,7 +46,9 @@
text-decoration: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: color 0.2s, border-color 0.2s;
transition:
color 0.2s,
border-color 0.2s;
}
.access-req-tab:hover {
+4 -4
View File
@@ -269,13 +269,13 @@
}
.admin-form--full-editor #editor .n-input {
height: 100% !important;
overflow-y: auto !important;
height: 100%;
overflow-y: auto;
}
.admin-form--full-editor #editor .n-preview {
height: 100% !important;
overflow-y: auto !important;
height: 100%;
overflow-y: auto;
}
.full-editor-toolbar {
+11 -7
View File
@@ -264,7 +264,9 @@
border: 1px solid var(--border-primary);
border-radius: var(--radius);
padding: var(--space-3xs) var(--space-xs);
transition: color 0.15s, border-color 0.15s;
transition:
color 0.15s,
border-color 0.15s;
}
.mode-toggle:hover {
@@ -1023,7 +1025,9 @@
color: var(--text-tertiary);
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s, color 0.15s;
transition:
background 0.15s,
color 0.15s;
}
.tag-pill-remove:hover {
@@ -1206,8 +1210,6 @@
color: var(--text-tertiary);
}
/* ── Sticky formats fieldset ──────────────────────────────── */
#fieldset-formats {
@@ -1240,7 +1242,7 @@ legend {
.licence-or-separator::before,
.licence-or-separator::after {
content: '';
content: "";
flex: 1;
height: 1px;
background: var(--accent-muted);
@@ -1262,7 +1264,6 @@ legend {
padding: 0 var(--space-2xs);
}
/* ── Mobile-responsive layout ──────────────────────────────────────────── */
/* Below 600px: labels stack above inputs, single-column form layout.
Touch targets meet WCAG 2.5.5 minimum (44×44px). */
@@ -1293,7 +1294,10 @@ legend {
.admin-form select,
.admin-form textarea,
.admin-form input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]) {
.admin-form
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not(
[type="hidden"]
):not([type="submit"]) {
min-height: 44px;
}
+21 -13
View File
@@ -140,12 +140,24 @@
}
/* Flex order: years/ap/or/fi first, then keywords, students last */
.repertoire-col[data-col="years"] { order: 0; }
.repertoire-col[data-col="ap"] { order: 1; }
.repertoire-col[data-col="or"] { order: 2; }
.repertoire-col[data-col="fi"] { order: 3; }
.repertoire-col[data-col="kw"] { order: 4; }
.repertoire-col[data-col="students"] { order: 5; }
.repertoire-col[data-col="years"] {
order: 0;
}
.repertoire-col[data-col="ap"] {
order: 1;
}
.repertoire-col[data-col="or"] {
order: 2;
}
.repertoire-col[data-col="fi"] {
order: 3;
}
.repertoire-col[data-col="kw"] {
order: 4;
}
.repertoire-col[data-col="students"] {
order: 5;
}
/* Students column: not an accordion — open results list below filters */
.repertoire-col[data-col="students"] {
@@ -293,8 +305,6 @@
}
}
/* ---- rep-entry: shared base (button + link variants) ---- */
.rep-entry {
display: block;
@@ -338,7 +348,7 @@
/* Selected: accent color */
.rep-entry--selected {
color: var(--accent-primary) !important;
color: var(--accent-primary);
}
/* Faded/disabled: muted, not interactive */
@@ -592,7 +602,6 @@
padding: 0 var(--space-2xs);
}
/* Error message */
.search-error {
background: var(--search-error-bg);
@@ -611,7 +620,7 @@
width: 300px;
background: var(--bg-primary, #fff);
border: 1px solid var(--border-primary, #ddd);
box-shadow: 0 4px 20px rgba(0,0,0,.13);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.13);
border-radius: var(--radius);
overflow: hidden;
pointer-events: auto;
@@ -648,7 +657,6 @@
}
.student-card__banner--gradient {
background-image: none;
background: linear-gradient(
180deg,
rgba(60, 133, 108, 1) 0%,
@@ -742,7 +750,7 @@
}
.search-filters-summary::after {
content: '';
content: "";
display: inline-block;
width: 8px;
height: 8px;
+42 -13
View File
@@ -3,12 +3,16 @@
originally used). Do not modify this file.
============================================================ */
*, ::before, ::after {
*,
::before,
::after {
box-sizing: border-box;
}
html {
font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
font-family:
system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
line-height: 1.15;
-webkit-text-size-adjust: 100%;
tab-size: 4;
@@ -18,12 +22,17 @@ body {
margin: 0;
}
b, strong {
b,
strong {
font-weight: bolder;
}
code, kbd, pre, samp {
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
code,
kbd,
pre,
samp {
font-family:
ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 1em;
}
@@ -31,28 +40,40 @@ small {
font-size: 80%;
}
sub, sup {
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub { bottom: -0.25em; }
sup { top: -0.5em; }
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
border-color: currentcolor;
}
button, input, optgroup, select, textarea {
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
[type=button], [type=reset], [type=submit], button {
[type="button"],
[type="reset"],
[type="submit"],
button {
-webkit-appearance: button;
}
@@ -69,7 +90,7 @@ progress {
height: auto;
}
[type=search] {
[type="search"] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
@@ -89,12 +110,20 @@ summary {
/* Additional minimal resets not in modern-normalize */
img, svg, video, canvas, audio, iframe, embed, object {
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
vertical-align: middle;
}
img, video {
img,
video {
max-width: 100%;
height: auto;
}
+77 -30
View File
@@ -18,7 +18,9 @@
text-decoration: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: color .15s, border-color .15s;
transition:
color 0.15s,
border-color 0.15s;
}
.sys-tab:hover {
color: var(--accent-primary);
@@ -52,7 +54,9 @@
padding: var(--space-3xs) var(--space-2xs);
cursor: pointer;
white-space: nowrap;
transition: color .15s, border-color .15s;
transition:
color 0.15s,
border-color 0.15s;
}
.sys-status-toggle:hover {
color: var(--text-primary);
@@ -67,7 +71,9 @@
border-top: 1px solid var(--border-primary);
}
@media (max-width: 700px) {
.sys-status-meta { grid-template-columns: 1fr; }
.sys-status-meta {
grid-template-columns: 1fr;
}
}
/* ── Status cards ──────────────────────────────────────────────────────── */
@@ -92,7 +98,7 @@
.srv-card__name {
font-size: var(--step--1);
text-transform: uppercase;
letter-spacing: .07em;
letter-spacing: 0.07em;
color: var(--text-secondary);
font-weight: 500;
}
@@ -102,15 +108,31 @@
margin-top: var(--space-3xs);
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
}
.status-ok { color: var(--accent-green); font-weight: 600; font-size: var(--step--1); }
.status-warn { color: var(--warning); font-weight: 600; font-size: var(--step--1); }
.status-err { color: var(--error); font-weight: 600; font-size: var(--step--1); }
.status-unknown { color: var(--text-tertiary); font-weight: 600; font-size: var(--step--1); }
.status-ok {
color: var(--accent-green);
font-weight: 600;
font-size: var(--step--1);
}
.status-warn {
color: var(--warning);
font-weight: 600;
font-size: var(--step--1);
}
.status-err {
color: var(--error);
font-weight: 600;
font-size: var(--step--1);
}
.status-unknown {
color: var(--text-tertiary);
font-weight: 600;
font-size: var(--step--1);
}
.srv-section-title {
font-size: var(--step--1);
text-transform: uppercase;
letter-spacing: .1em;
letter-spacing: 0.1em;
color: var(--text-secondary);
border-bottom: 1px solid var(--border-primary);
padding-bottom: var(--space-3xs);
@@ -124,7 +146,9 @@
padding: 0;
}
/* Sub-section variant: tighter bottom margin — used for PHP/disk sub-headings */
.srv-section-title--sub { margin-bottom: var(--space-xs); }
.srv-section-title--sub {
margin-bottom: var(--space-xs);
}
/* ── PHP info grid ─────────────────────────────────────────────────────── */
.php-grid {
@@ -134,7 +158,9 @@
margin-bottom: var(--space-l);
}
/* Flush variant: no bottom margin — used inside sys-status-meta cell */
.php-grid--flush { margin-bottom: 0; }
.php-grid--flush {
margin-bottom: 0;
}
.php-item {
background: var(--bg-primary);
border: 1px solid var(--border-primary);
@@ -144,7 +170,7 @@
.php-item__key {
font-size: var(--step--2);
text-transform: uppercase;
letter-spacing: .06em;
letter-spacing: 0.06em;
color: var(--text-tertiary);
}
.php-item__val {
@@ -167,7 +193,7 @@
border-radius: var(--radius);
width: var(--disk-pct, 0%);
background: var(--disk-color, var(--accent-green));
transition: width .3s;
transition: width 0.3s;
}
.disk-stats {
display: flex;
@@ -188,7 +214,7 @@
transition: opacity 0.1s;
}
#sys-tab-panel.htmx-request::after {
content: '';
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(
@@ -203,8 +229,12 @@
background-size: 200% 200%;
}
@keyframes sys-panel-shimmer {
0% { background-position: 0 0; }
100% { background-position: 100% 100%; }
0% {
background-position: 0 0;
}
100% {
background-position: 100% 100%;
}
}
/* ── Log viewer ────────────────────────────────────────────────────────── */
@@ -222,7 +252,9 @@
.log-toolbar select {
padding: var(--space-3xs) var(--space-xs);
}
.log-toolbar select:focus { outline: 2px solid var(--accent-primary); }
.log-toolbar select:focus {
outline: 2px solid var(--accent-primary);
}
.log-meta {
font-size: var(--step--2);
@@ -233,7 +265,10 @@
gap: var(--space-m);
flex-wrap: wrap;
}
.log-meta span::before { content: attr(data-label) ": "; opacity: .6; }
.log-meta span::before {
content: attr(data-label) ": ";
opacity: 0.6;
}
.log-unavailable {
background: var(--bg-secondary);
@@ -247,12 +282,12 @@
font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
font-size: var(--step--2);
margin-top: var(--space-3xs);
opacity: .7;
opacity: 0.7;
}
.log-unavail-dev {
margin-top: var(--space-2xs);
font-size: var(--step--2);
opacity: .7;
opacity: 0.7;
}
.log-empty {
color: var(--text-secondary);
@@ -265,7 +300,8 @@
border-radius: var(--radius);
padding: var(--space-s);
overflow-x: auto;
font-family: ui-monospace, "SFMono-Regular", Consolas, "Courier New", monospace;
font-family:
ui-monospace, "SFMono-Regular", Consolas, "Courier New", monospace;
font-size: var(--step--2);
line-height: 1.55;
max-height: 62vh;
@@ -279,17 +315,28 @@
border-radius: var(--radius);
color: var(--text-primary);
}
.log-line + .log-line { border-top: 1px solid var(--border-primary); }
.log-crit { color: var(--sys-syntax-crit); background: var(--error-muted-bg); }
.log-error { color: var(--error); }
.log-warn { color: var(--warning); }
.log-notice { color: var(--sys-syntax-notice); }
.log-line + .log-line {
border-top: 1px solid var(--border-primary);
}
.log-crit {
color: var(--sys-syntax-crit);
background: var(--error-muted-bg);
}
.log-error {
color: var(--error);
}
.log-warn {
color: var(--warning);
}
.log-notice {
color: var(--sys-syntax-notice);
}
.log-line::before {
content: attr(data-n);
display: inline-block;
min-width: 4ch;
margin-right: var(--space-xs);
opacity: .3;
opacity: 0.3;
text-align: right;
user-select: none;
}
@@ -327,7 +374,9 @@
color: var(--accent-primary);
text-decoration: none;
}
.sys-refresh-note a:hover { color: var(--accent-primary); }
.sys-refresh-note a:hover {
color: var(--accent-primary);
}
/* ── Cache freshness badges ────────────────────────────────────────────── */
.sys-cache-badge {
@@ -351,5 +400,3 @@
color: var(--success);
border: 1px solid var(--success-muted-border);
}
+2 -2
View File
@@ -45,7 +45,7 @@
*,
*::before,
*::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
transition-duration: 0.01ms;
animation-duration: 0.01ms;
}
}
@@ -4,43 +4,45 @@
* Provides: dialog openers, clipboard copy, password dialog.
*/
(() => {
var createBtn = document.getElementById('open-create-dialog');
var createBtn = document.getElementById("open-create-dialog");
if (createBtn) {
createBtn.addEventListener('click', () => {
document.getElementById('create-dialog').showModal();
createBtn.addEventListener("click", () => {
document.getElementById("create-dialog").showModal();
});
}
})();
var _pendingDeleteLinkId = null;
function openDeleteLinkDialog(id) {
function _openDeleteLinkDialog(id) {
_pendingDeleteLinkId = id;
document.getElementById('delete-link-dialog').showModal();
document.getElementById("delete-link-dialog").showModal();
}
function _executeDeleteLink() {
var form = document.getElementById('delete-link-form-' + _pendingDeleteLinkId);
var form = document.getElementById(
`delete-link-form-${_pendingDeleteLinkId}`,
);
if (form) form.submit();
}
function copyUrl(id) {
var input = document.getElementById('url-' + id);
function _copyUrl(id) {
var input = document.getElementById(`url-${id}`);
navigator.clipboard.writeText(input.value).then(() => {
var btn = event.target.closest('button');
var btn = event.target.closest("button");
var orig = btn.textContent;
btn.textContent = '✓ Copié';
btn.textContent = "✓ Copié";
setTimeout(() => {
btn.textContent = orig;
}, 1200);
});
}
function openPasswordDialog(id, hasPassword) {
document.getElementById('password-link-id').value = id;
var info = document.getElementById('password-current-info');
function _openPasswordDialog(id, hasPassword) {
document.getElementById("password-link-id").value = id;
var info = document.getElementById("password-current-info");
info.textContent = hasPassword
? 'Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer.'
: 'Aucun mot de passe configuré.';
document.getElementById('password-dialog').showModal();
? "Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer."
: "Aucun mot de passe configuré.";
document.getElementById("password-dialog").showModal();
}
+47 -41
View File
@@ -8,46 +8,49 @@
*/
(() => {
var baseUrlMeta = document.querySelector('meta[name="acces-base-url"]');
var passwordMeta = document.querySelector('meta[name="acces-new-link-password"]');
var passwordMeta = document.querySelector(
'meta[name="acces-new-link-password"]',
);
var slugMeta = document.querySelector('meta[name="acces-new-link-slug"]');
// Show result dialog after redirect (new link created)
if (passwordMeta && slugMeta && passwordMeta.content && slugMeta.content) {
document.getElementById('create-result-password').value = passwordMeta.content;
document.getElementById('create-result-url').value =
(baseUrlMeta ? baseUrlMeta.content : '') + '/partage/' + slugMeta.content;
document.getElementById('create-result-dialog').showModal();
document.getElementById("create-result-password").value =
passwordMeta.content;
document.getElementById("create-result-url").value =
`${baseUrlMeta ? baseUrlMeta.content : ""}/partage/${slugMeta.content}`;
document.getElementById("create-result-dialog").showModal();
}
// Create dialog opener
var createBtn = document.getElementById('open-create-dialog');
var createBtn = document.getElementById("open-create-dialog");
if (createBtn) {
createBtn.addEventListener('click', () => {
document.getElementById('create-dialog').showModal();
createBtn.addEventListener("click", () => {
document.getElementById("create-dialog").showModal();
});
}
})();
function copyUrl(id) {
var input = document.getElementById('url-' + id);
function _copyUrl(id) {
var input = document.getElementById(`url-${id}`);
navigator.clipboard.writeText(input.value).then(() => {
var btn = event.target.closest('button');
var btn = event.target.closest("button");
if (btn) {
var orig = btn.getAttribute('title') || '';
btn.setAttribute('title', '✓ Copié');
const orig = btn.getAttribute("title") || "";
btn.setAttribute("title", "✓ Copié");
setTimeout(() => {
btn.setAttribute('title', orig);
btn.setAttribute("title", orig);
}, 1200);
}
});
}
function copyUrlFrom(el) {
function _copyUrlFrom(el) {
navigator.clipboard.writeText(el.value).then(() => {
var btn = el.nextElementSibling;
const btn = el.nextElementSibling;
if (btn) {
var orig = btn.textContent;
btn.textContent = '✓ Copié';
const orig = btn.textContent;
btn.textContent = "✓ Copié";
setTimeout(() => {
btn.textContent = orig;
}, 1200);
@@ -55,51 +58,54 @@ function copyUrlFrom(el) {
});
}
function copyTextToClipboard(text) {
navigator.clipboard.writeText(text)
function _copyTextToClipboard(text) {
navigator.clipboard
.writeText(text)
.then(() => {
var btn = event && event.target ? event.target.closest('button') : null;
const btn = event?.target ? event.target.closest("button") : null;
if (btn) {
var orig = btn.getAttribute('title') || '';
btn.setAttribute('title', '✓ Copié');
const orig = btn.getAttribute("title") || "";
btn.setAttribute("title", "✓ Copié");
setTimeout(() => {
btn.setAttribute('title', orig);
btn.setAttribute("title", orig);
}, 1200);
}
})
.catch(() => {});
}
function openEditDialog(id, name, hasPassword, expiresVal) {
document.getElementById('edit-link-id').value = id;
document.getElementById('edit-name').value = name || '';
document.getElementById('edit-expires').value = expiresVal || '';
document.getElementById('edit-dialog').showModal();
function _openEditDialog(id, name, _hasPassword, expiresVal) {
document.getElementById("edit-link-id").value = id;
document.getElementById("edit-name").value = name || "";
document.getElementById("edit-expires").value = expiresVal || "";
document.getElementById("edit-dialog").showModal();
}
function openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId;
document.getElementById('approve-dialog').showModal();
function _openApproveDialog(requestId) {
document.getElementById("approve-request-id").value = requestId;
document.getElementById("approve-dialog").showModal();
}
function openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId;
document.getElementById('reject-dialog').showModal();
function _openRejectDialog(requestId) {
document.getElementById("reject-request-id").value = requestId;
document.getElementById("reject-dialog").showModal();
}
var _pendingArchiveLinkId = null;
function openArchiveLinkDialog(id) {
function _openArchiveLinkDialog(id) {
_pendingArchiveLinkId = id;
document.getElementById('archive-link-dialog').showModal();
document.getElementById("archive-link-dialog").showModal();
}
function _executeArchiveLink() {
var form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId);
var form = document.getElementById(
`archive-link-form-${_pendingArchiveLinkId}`,
);
if (form) form.submit();
}
function openDeleteArchivedLinkDialog(id) {
document.getElementById('delete-archived-link-id').value = id;
document.getElementById('delete-archived-link-dialog').showModal();
function _openDeleteArchivedLinkDialog(id) {
document.getElementById("delete-archived-link-id").value = id;
document.getElementById("delete-archived-link-dialog").showModal();
}
+25 -20
View File
@@ -6,7 +6,8 @@
*/
(() => {
function cleanupToggleAll(src, group) {
const selector = group === 'filepond'
const selector =
group === "filepond"
? 'input[name="filepond_dirs[]"]'
: 'input[name="trash_files[]"]';
document.querySelectorAll(selector).forEach((cb) => {
@@ -16,50 +17,54 @@
}
function cleanupUpdateBulk() {
const bar = document.getElementById('cleanup-bulk-actions');
const countEl = document.getElementById('cleanup-selected-count');
const bar = document.getElementById("cleanup-bulk-actions");
const countEl = document.getElementById("cleanup-selected-count");
if (!bar || !countEl) return;
const n = document.querySelectorAll(
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked'
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
).length;
countEl.textContent = n;
bar.style.display = n > 0 ? 'flex' : 'none';
bar.style.display = n > 0 ? "flex" : "none";
}
function cleanupBulkDelete() {
const form = document.getElementById('cleanup-bulk-form');
const container = document.getElementById('cleanup-bulk-checkboxes');
const form = document.getElementById("cleanup-bulk-form");
const container = document.getElementById("cleanup-bulk-checkboxes");
if (!form || !container) return;
// Populate hidden inputs from checked checkboxes
container.innerHTML = '';
document.querySelectorAll(
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked'
).forEach((cb) => {
const inp = document.createElement('input');
inp.type = 'hidden';
container.innerHTML = "";
document
.querySelectorAll(
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
)
.forEach((cb) => {
const inp = document.createElement("input");
inp.type = "hidden";
inp.name = cb.name; // "filepond_dirs[]" or "trash_files[]"
inp.value = cb.value;
container.appendChild(inp);
});
// Submit via HTMX
htmx.trigger(form, 'submit');
htmx.trigger(form, "submit");
}
function reattachListeners() {
document.querySelectorAll(
'input[name="filepond_dirs[]"], input[name="trash_files[]"]'
).forEach((cb) => {
cb.addEventListener('change', cleanupUpdateBulk);
document
.querySelectorAll(
'input[name="filepond_dirs[]"], input[name="trash_files[]"]',
)
.forEach((cb) => {
cb.addEventListener("change", cleanupUpdateBulk);
});
cleanupUpdateBulk();
}
document.addEventListener('htmx:afterSwap', (evt) => {
document.addEventListener("htmx:afterSwap", (evt) => {
// Only reattach if the swap target is our cleanup wrapper
if (evt.detail.target.id === 'tmp-cleanup-stats-wrapper') {
if (evt.detail.target.id === "tmp-cleanup-stats-wrapper") {
reattachListeners();
}
});
+33 -32
View File
@@ -5,62 +5,63 @@
* Expects templates with ids `entry-template-f-{key}` and `group-template-f-{key}`.
*/
(() => {
var form = document.querySelector('form[data-apropos-key]');
var form = document.querySelector("form[data-apropos-key]");
if (!form) return;
var key = form.dataset.aproposKey;
var groupCount = parseInt(form.dataset.aproposGroupCount, 10) || 0;
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML;
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML;
var entryTpl = document.getElementById(`entry-template-f-${key}`).innerHTML;
var groupTpl = document.getElementById(`group-template-f-${key}`).innerHTML;
function reindexGroups() {
var fieldsets = form.querySelectorAll('fieldset.apropos-group');
var fieldsets = form.querySelectorAll("fieldset.apropos-group");
groupCount = fieldsets.length;
fieldsets.forEach((fs, i) => {
var newIdx = i;
var legend = fs.querySelector('legend');
if (legend) legend.textContent = 'Contact ' + (newIdx + 1);
var legend = fs.querySelector("legend");
if (legend) legend.textContent = `Contact ${newIdx + 1}`;
fs.querySelectorAll('input').forEach((inp) => {
fs.querySelectorAll("input").forEach((inp) => {
if (inp.name) {
inp.name = inp.name.replace(/groups\[\d+\]/, 'groups[' + newIdx + ']');
inp.name = inp.name.replace(/groups\[\d+\]/, `groups[${newIdx}]`);
}
if (inp.id) {
inp.id = inp.id.replace(
/(group_f_contacts_|entry_f_contacts_)\d+/,
'$1' + newIdx
`$1${newIdx}`,
);
}
});
fs.querySelectorAll('label[for]').forEach((lbl) => {
fs.querySelectorAll("label[for]").forEach((lbl) => {
lbl.setAttribute(
'for',
lbl.getAttribute('for').replace(
/(group_f_contacts_|entry_f_contacts_)\d+/,
'$1' + newIdx
)
"for",
lbl
.getAttribute("for")
.replace(/(group_f_contacts_|entry_f_contacts_)\d+/, `$1${newIdx}`),
);
});
var addBtn = fs.querySelector('.add-entry-btn-f');
var addBtn = fs.querySelector(".add-entry-btn-f");
if (addBtn) addBtn.dataset.group = newIdx;
});
}
function bindAddEntry(btn) {
btn.addEventListener('click', function () {
var gi = parseInt(this.dataset.group);
var fieldset = this.closest('fieldset');
var entryCount = fieldset.querySelectorAll('.apropos-entry').length;
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
this.insertAdjacentHTML('beforebegin', html);
btn.addEventListener("click", function () {
var gi = parseInt(this.dataset.group, 10);
var fieldset = this.closest("fieldset");
var entryCount = fieldset.querySelectorAll(".apropos-entry").length;
var html = entryTpl
.replaceAll("{{gi}}", gi)
.replaceAll("{{ei}}", entryCount);
this.insertAdjacentHTML("beforebegin", html);
});
}
function bindDeleteGroup(btn) {
btn.addEventListener('click', function () {
var fieldset = this.closest('fieldset');
btn.addEventListener("click", function () {
var fieldset = this.closest("fieldset");
if (fieldset) {
fieldset.remove();
reindexGroups();
@@ -69,22 +70,22 @@
}
// Bind existing buttons
form.querySelectorAll('.add-entry-btn-f').forEach(bindAddEntry);
form.querySelectorAll('.delete-group-btn-f').forEach(bindDeleteGroup);
form.querySelectorAll(".add-entry-btn-f").forEach(bindAddEntry);
form.querySelectorAll(".delete-group-btn-f").forEach(bindDeleteGroup);
form.querySelector('.add-group-btn-f').addEventListener('click', function () {
form.querySelector(".add-group-btn-f").addEventListener("click", function () {
groupCount++;
var html = groupTpl.replaceAll('{{gi}}', groupCount);
this.insertAdjacentHTML('beforebegin', html);
var html = groupTpl.replaceAll("{{gi}}", groupCount);
this.insertAdjacentHTML("beforebegin", html);
var newGroup = this.previousElementSibling;
if (newGroup && newGroup.classList.contains('apropos-group')) {
var addBtn = newGroup.querySelector('.add-entry-btn-f');
if (newGroup?.classList.contains("apropos-group")) {
var addBtn = newGroup.querySelector(".add-entry-btn-f");
if (addBtn) {
addBtn.dataset.group = groupCount;
bindAddEntry(addBtn);
}
var delBtn = newGroup.querySelector('.delete-group-btn-f');
var delBtn = newGroup.querySelector(".delete-group-btn-f");
if (delBtn) bindDeleteGroup(delBtn);
}
});
@@ -7,17 +7,17 @@
*/
var _languesPendingForm = null;
function languesConfirmDelete(btn, name) {
_languesPendingForm = btn.closest('form');
document.getElementById('langues-delete-name').textContent = name;
document.getElementById('langues-delete-dialog').showModal();
function _languesConfirmDelete(btn, name) {
_languesPendingForm = btn.closest("form");
document.getElementById("langues-delete-name").textContent = name;
document.getElementById("langues-delete-dialog").showModal();
}
function languesSubmitPending() {
function _languesSubmitPending() {
if (_languesPendingForm) _languesPendingForm.submit();
}
function languesToggleAll(src) {
function _languesToggleAll(src) {
document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => {
cb.checked = src.checked;
});
@@ -25,92 +25,107 @@ function languesToggleAll(src) {
}
function languesUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_langs[]"]:checked').length;
document.getElementById('langues-selected-count').textContent = n;
var bar = document.getElementById('langues-bulk-actions');
var wrap = document.getElementById('langues-table-wrap');
var n = document.querySelectorAll(
'input[name="selected_langs[]"]:checked',
).length;
document.getElementById("langues-selected-count").textContent = n;
var bar = document.getElementById("langues-bulk-actions");
var wrap = document.getElementById("langues-table-wrap");
var visible = n > 1;
bar.style.display = visible ? 'flex' : 'none';
bar.style.display = visible ? "flex" : "none";
if (visible) {
requestAnimationFrame(() => {
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px');
wrap.style.setProperty("--sticky-top", `${bar.offsetHeight}px`);
});
} else {
wrap.style.setProperty('--sticky-top', '0px');
wrap.style.setProperty("--sticky-top", "0px");
}
}
function languesCancelSelection() {
function _languesCancelSelection() {
document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => {
cb.checked = false;
});
languesUpdateBulk();
}
function languesConfirmBulkDelete() {
var checked = document.querySelectorAll('input[name="selected_langs[]"]:checked');
function _languesConfirmBulkDelete() {
var checked = document.querySelectorAll(
'input[name="selected_langs[]"]:checked',
);
if (checked.length < 1) return;
document.getElementById('langues-bulk-delete-count').textContent = checked.length;
document.getElementById('langues-bulk-delete-dialog').showModal();
document.getElementById("langues-bulk-delete-count").textContent =
checked.length;
document.getElementById("langues-bulk-delete-dialog").showModal();
}
function languesExecBulkDelete() {
var container = document.getElementById('langues-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach((cb) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_langs[]';
function _languesExecBulkDelete() {
var container = document.getElementById("langues-bulk-checkboxes");
container.innerHTML = "";
document
.querySelectorAll('input[name="selected_langs[]"]:checked')
.forEach((cb) => {
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_langs[]";
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('langues-bulk-form').querySelector('input[name="action"]').value =
'delete_bulk';
document.getElementById('langues-bulk-delete-dialog').close();
document.getElementById('langues-bulk-form').submit();
document
.getElementById("langues-bulk-form")
.querySelector('input[name="action"]').value = "delete_bulk";
document.getElementById("langues-bulk-delete-dialog").close();
document.getElementById("langues-bulk-form").submit();
}
function languesConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_langs[]"]:checked');
function _languesConfirmBulkMerge() {
var checked = document.querySelectorAll(
'input[name="selected_langs[]"]:checked',
);
if (checked.length < 2) return;
document.getElementById('langues-bulk-merge-count').textContent = checked.length;
var sel = document.getElementById('langues-bulk-merge-target-select');
document.getElementById("langues-bulk-merge-count").textContent =
checked.length;
var sel = document.getElementById("langues-bulk-merge-target-select");
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
checked.forEach((cb) => {
var tr = cb.closest('tr');
var tr = cb.closest("tr");
sel.innerHTML +=
'<option value="' +
cb.value +
'">' +
tr.querySelector('td:nth-child(2)').textContent.trim() +
'</option>';
tr.querySelector("td:nth-child(2)").textContent.trim() +
"</option>";
});
document.getElementById('langues-bulk-merge-dialog').showModal();
document.getElementById("langues-bulk-merge-dialog").showModal();
}
function languesExecBulkMerge() {
var targetId = document.getElementById('langues-bulk-merge-target-select').value;
function _languesExecBulkMerge() {
var targetId = document.getElementById(
"langues-bulk-merge-target-select",
).value;
if (!targetId) return;
document.getElementById('langues-bulk-target').value = targetId;
var container = document.getElementById('langues-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach((cb) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_langs[]';
document.getElementById("langues-bulk-target").value = targetId;
var container = document.getElementById("langues-bulk-checkboxes");
container.innerHTML = "";
document
.querySelectorAll('input[name="selected_langs[]"]:checked')
.forEach((cb) => {
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_langs[]";
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('langues-bulk-merge-dialog').close();
document.getElementById('langues-bulk-form').submit();
document.getElementById("langues-bulk-merge-dialog").close();
document.getElementById("langues-bulk-form").submit();
}
document.addEventListener('htmx:afterSwap', (evt) => {
if (evt.target.id === 'langues-table-wrap') {
document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === "langues-table-wrap") {
document
.querySelectorAll('input[name="selected_langs[]"]')
.forEach((cb) => {
cb.addEventListener('change', languesUpdateBulk);
cb.addEventListener("change", languesUpdateBulk);
});
languesUpdateBulk();
}
@@ -7,17 +7,17 @@
*/
var _motsclesPendingForm = null;
function motsclesConfirmDelete(btn, name) {
_motsclesPendingForm = btn.closest('form');
document.getElementById('motscles-delete-name').textContent = name;
document.getElementById('motscles-delete-dialog').showModal();
function _motsclesConfirmDelete(btn, name) {
_motsclesPendingForm = btn.closest("form");
document.getElementById("motscles-delete-name").textContent = name;
document.getElementById("motscles-delete-dialog").showModal();
}
function motsclesSubmitPending() {
function _motsclesSubmitPending() {
if (_motsclesPendingForm) _motsclesPendingForm.submit();
}
function motsclesToggleAll(src) {
function _motsclesToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = src.checked;
});
@@ -25,92 +25,105 @@ function motsclesToggleAll(src) {
}
function motsclesUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length;
document.getElementById('motscles-selected-count').textContent = n;
var bar = document.getElementById('motscles-bulk-actions');
var wrap = document.getElementById('motscles-table-wrap');
var n = document.querySelectorAll(
'input[name="selected_tags[]"]:checked',
).length;
document.getElementById("motscles-selected-count").textContent = n;
var bar = document.getElementById("motscles-bulk-actions");
var wrap = document.getElementById("motscles-table-wrap");
var visible = n > 1;
bar.style.display = visible ? 'flex' : 'none';
bar.style.display = visible ? "flex" : "none";
if (visible) {
requestAnimationFrame(() => {
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px');
wrap.style.setProperty("--sticky-top", `${bar.offsetHeight}px`);
});
} else {
wrap.style.setProperty('--sticky-top', '0px');
wrap.style.setProperty("--sticky-top", "0px");
}
}
function motsclesConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
function _motsclesConfirmBulkMerge() {
var checked = document.querySelectorAll(
'input[name="selected_tags[]"]:checked',
);
if (checked.length < 2) return;
document.getElementById('motscles-bulk-merge-count').textContent = checked.length;
var sel = document.getElementById('motscles-bulk-merge-target-select');
document.getElementById("motscles-bulk-merge-count").textContent =
checked.length;
var sel = document.getElementById("motscles-bulk-merge-target-select");
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
checked.forEach((cb) => {
var tr = cb.closest('tr');
var tr = cb.closest("tr");
sel.innerHTML +=
'<option value="' +
cb.value +
'">' +
tr.querySelector('td:nth-child(2)').textContent.trim() +
'</option>';
tr.querySelector("td:nth-child(2)").textContent.trim() +
"</option>";
});
document.getElementById('motscles-bulk-merge-dialog').showModal();
document.getElementById("motscles-bulk-merge-dialog").showModal();
}
function motsclesExecBulkMerge() {
var targetId = document.getElementById('motscles-bulk-merge-target-select').value;
function _motsclesExecBulkMerge() {
var targetId = document.getElementById(
"motscles-bulk-merge-target-select",
).value;
if (!targetId) return;
document.getElementById('motscles-bulk-target').value = targetId;
var container = document.getElementById('motscles-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_tags[]';
document.getElementById("motscles-bulk-target").value = targetId;
var container = document.getElementById("motscles-bulk-checkboxes");
container.innerHTML = "";
document
.querySelectorAll('input[name="selected_tags[]"]:checked')
.forEach((cb) => {
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_tags[]";
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('motscles-bulk-merge-dialog').close();
document.getElementById('motscles-bulk-form').submit();
document.getElementById("motscles-bulk-merge-dialog").close();
document.getElementById("motscles-bulk-form").submit();
}
function motsclesCancelSelection() {
function _motsclesCancelSelection() {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = false;
});
motsclesUpdateBulk();
}
function motsclesConfirmBulkDelete() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
function _motsclesConfirmBulkDelete() {
var checked = document.querySelectorAll(
'input[name="selected_tags[]"]:checked',
);
if (checked.length < 1) return;
document.getElementById('motscles-bulk-delete-count').textContent = checked.length;
document.getElementById('motscles-bulk-delete-dialog').showModal();
document.getElementById("motscles-bulk-delete-count").textContent =
checked.length;
document.getElementById("motscles-bulk-delete-dialog").showModal();
}
function motsclesExecBulkDelete() {
var container = document.getElementById('motscles-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_tags[]';
function _motsclesExecBulkDelete() {
var container = document.getElementById("motscles-bulk-checkboxes");
container.innerHTML = "";
document
.querySelectorAll('input[name="selected_tags[]"]:checked')
.forEach((cb) => {
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_tags[]";
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('motscles-bulk-form').querySelector('input[name="action"]').value =
'delete_bulk';
document.getElementById('motscles-bulk-delete-dialog').close();
document.getElementById('motscles-bulk-form').submit();
document
.getElementById("motscles-bulk-form")
.querySelector('input[name="action"]').value = "delete_bulk";
document.getElementById("motscles-bulk-delete-dialog").close();
document.getElementById("motscles-bulk-form").submit();
}
document.addEventListener('htmx:afterSwap', (evt) => {
if (evt.target.id === 'motscles-table-wrap') {
document
.querySelectorAll('input[name="selected_tags[]"]')
.forEach((cb) => {
cb.addEventListener('change', motsclesUpdateBulk);
document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === "motscles-table-wrap") {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.addEventListener("change", motsclesUpdateBulk);
});
motsclesUpdateBulk();
}
@@ -3,12 +3,12 @@
*
* Provides: openApproveDialog, openRejectDialog.
*/
function openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId;
document.getElementById('approve-dialog').showModal();
function _openApproveDialog(requestId) {
document.getElementById("approve-request-id").value = requestId;
document.getElementById("approve-dialog").showModal();
}
function openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId;
document.getElementById('reject-dialog').showModal();
function _openRejectDialog(requestId) {
document.getElementById("reject-request-id").value = requestId;
document.getElementById("reject-dialog").showModal();
}
+44 -39
View File
@@ -6,33 +6,36 @@
*/
(() => {
function toggleAll(src) {
document.querySelectorAll('input[name="selected_theses[]"]').forEach((cb) => {
document
.querySelectorAll('input[name="selected_theses[]"]')
.forEach((cb) => {
cb.checked = src.checked;
});
updateBulk();
}
function updateBulk() {
var b = document.getElementById('bulk-actions');
var c = document.getElementById('selected-count');
var b = document.getElementById("bulk-actions");
var c = document.getElementById("selected-count");
if (!b || !c) return; // only on thesis index page
var n = document.querySelectorAll('input[name="selected_theses[]"]:checked').length;
var n = document.querySelectorAll(
'input[name="selected_theses[]"]:checked',
).length;
c.textContent = n;
b.style.display = n > 0 ? 'flex' : 'none';
document.getElementById('admin-table-wrap').style.setProperty(
'--sticky-top',
n > 0 ? b.offsetHeight + 'px' : '0px'
);
b.style.display = n > 0 ? "flex" : "none";
document
.getElementById("admin-table-wrap")
.style.setProperty("--sticky-top", n > 0 ? `${b.offsetHeight}px` : "0px");
}
function getSelectedIds() {
return Array.from(
document.querySelectorAll('input[name="selected_theses[]"]:checked')
document.querySelectorAll('input[name="selected_theses[]"]:checked'),
).map((cb) => cb.value);
}
function confirmBulk(act) {
var noSel = document.getElementById('no-selection-dialog');
var noSel = document.getElementById("no-selection-dialog");
if (!noSel) return; // only on thesis index page
var ids = getSelectedIds();
if (!ids.length) {
@@ -40,28 +43,28 @@
return;
}
var n = ids.length;
document.getElementById('bulk-action-input').value = act;
if (act === 'delete') {
document.getElementById('bulk-delete-count').textContent = n;
document.getElementById('bulk-delete-dialog').showModal();
document.getElementById("bulk-action-input").value = act;
if (act === "delete") {
document.getElementById("bulk-delete-count").textContent = n;
document.getElementById("bulk-delete-dialog").showModal();
} else {
document.getElementById('bulk-confirm-word').textContent =
act === 'publish' ? 'Publier' : 'Dépublier';
document.getElementById('bulk-confirm-count').textContent = n;
document.getElementById('bulk-confirm-dialog').showModal();
document.getElementById("bulk-confirm-word").textContent =
act === "publish" ? "Publier" : "Dépublier";
document.getElementById("bulk-confirm-count").textContent = n;
document.getElementById("bulk-confirm-dialog").showModal();
}
}
function execBulk() {
var a = document.getElementById('bulk-action-input').value;
var f = document.getElementById('bulk-form');
f.action = a === 'delete' ? 'actions/delete.php' : 'actions/publish.php';
var c = document.getElementById('bulk-checkboxes');
c.innerHTML = '';
var a = document.getElementById("bulk-action-input").value;
var f = document.getElementById("bulk-form");
f.action = a === "delete" ? "actions/delete.php" : "actions/publish.php";
var c = document.getElementById("bulk-checkboxes");
c.innerHTML = "";
getSelectedIds().forEach((id) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_theses[]';
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_theses[]";
inp.value = id;
c.appendChild(inp);
});
@@ -71,40 +74,42 @@
function confirmExport() {
var ids = getSelectedIds();
if (!ids.length) {
document.getElementById('no-selection-dialog').showModal();
document.getElementById("no-selection-dialog").showModal();
return;
}
window.location.href = '/admin/actions/export.php?csv=1&ids=' + ids.join(',');
window.location.href = `/admin/actions/export.php?csv=1&ids=${ids.join(",")}`;
}
function confirmExportFiles() {
var ids = getSelectedIds();
if (!ids.length) {
document.getElementById('no-selection-dialog').showModal();
document.getElementById("no-selection-dialog").showModal();
return;
}
window.location.href = '/admin/actions/export.php?files=1&ids=' + ids.join(',');
window.location.href = `/admin/actions/export.php?files=1&ids=${ids.join(",")}`;
}
function confirmDelete(id, title) {
var dialog = document.getElementById('delete-thesis-dialog');
var dialog = document.getElementById("delete-thesis-dialog");
if (!dialog) return; // only on thesis index page
document.getElementById('delete-thesis-title').textContent = title;
document.getElementById("delete-thesis-title").textContent = title;
dialog.showModal();
document.getElementById('delete-dialog-confirm').onclick = () => {
document.getElementById('delete-form-' + id).submit();
document.getElementById("delete-dialog-confirm").onclick = () => {
document.getElementById(`delete-form-${id}`).submit();
};
}
function reattachListeners() {
document.querySelectorAll('input[name="selected_theses[]"]').forEach((cb) => {
cb.addEventListener('change', updateBulk);
document
.querySelectorAll('input[name="selected_theses[]"]')
.forEach((cb) => {
cb.addEventListener("change", updateBulk);
});
updateBulk();
}
document.addEventListener('DOMContentLoaded', reattachListeners);
document.addEventListener('htmx:afterSwap', reattachListeners);
document.addEventListener("DOMContentLoaded", reattachListeners);
document.addEventListener("htmx:afterSwap", reattachListeners);
// Export to global scope for onclick handlers in HTML
window.toggleAll = toggleAll;
+38 -33
View File
@@ -10,7 +10,7 @@
*/
var _pendingTagForm = null;
function tagsToggleAll(src) {
function _tagsToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.checked = src.checked;
});
@@ -18,62 +18,67 @@ function tagsToggleAll(src) {
}
function tagsUpdateBulk() {
var n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length;
document.getElementById('tags-selected-count').textContent = n;
document.getElementById('tags-bulk-actions').style.display = n > 1 ? 'flex' : 'none';
var n = document.querySelectorAll(
'input[name="selected_tags[]"]:checked',
).length;
document.getElementById("tags-selected-count").textContent = n;
document.getElementById("tags-bulk-actions").style.display =
n > 1 ? "flex" : "none";
}
function tagsConfirmBulkMerge() {
var checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
function _tagsConfirmBulkMerge() {
var checked = document.querySelectorAll(
'input[name="selected_tags[]"]:checked',
);
if (checked.length < 2) return;
document.getElementById('bulk-merge-count').textContent = checked.length;
var sel = document.getElementById('bulk-merge-target-select');
document.getElementById("bulk-merge-count").textContent = checked.length;
var sel = document.getElementById("bulk-merge-target-select");
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
checked.forEach((cb) => {
var tr = cb.closest('tr');
var tr = cb.closest("tr");
sel.innerHTML +=
'<option value="' +
cb.value +
'">' +
tr.querySelector('.tag-name-cell').textContent.trim() +
'</option>';
tr.querySelector(".tag-name-cell").textContent.trim() +
"</option>";
});
document.getElementById('bulk-merge-dialog').showModal();
document.getElementById("bulk-merge-dialog").showModal();
}
function tagsExecBulkMerge() {
var targetId = document.getElementById('bulk-merge-target-select').value;
function _tagsExecBulkMerge() {
var targetId = document.getElementById("bulk-merge-target-select").value;
if (!targetId) return;
document.getElementById('tags-bulk-target').value = targetId;
var container = document.getElementById('tags-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach((cb) => {
var inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_tags[]';
document.getElementById("tags-bulk-target").value = targetId;
var container = document.getElementById("tags-bulk-checkboxes");
container.innerHTML = "";
document
.querySelectorAll('input[name="selected_tags[]"]:checked')
.forEach((cb) => {
var inp = document.createElement("input");
inp.type = "hidden";
inp.name = "selected_tags[]";
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('bulk-merge-dialog').close();
document.getElementById('tags-bulk-form').submit();
document.getElementById("bulk-merge-dialog").close();
document.getElementById("tags-bulk-form").submit();
}
function tagsConfirmDelete(btn, name) {
_pendingTagForm = btn.closest('form');
document.getElementById('delete-tag-name').textContent = name;
document.getElementById('delete-tag-dialog').showModal();
function _tagsConfirmDelete(btn, name) {
_pendingTagForm = btn.closest("form");
document.getElementById("delete-tag-name").textContent = name;
document.getElementById("delete-tag-dialog").showModal();
}
function _submitPendingTagForm() {
if (_pendingTagForm) _pendingTagForm.submit();
}
document.addEventListener('htmx:afterSwap', (evt) => {
if (evt.target.id === 'tags-table-wrap') {
document
.querySelectorAll('input[name="selected_tags[]"]')
.forEach((cb) => {
cb.addEventListener('change', tagsUpdateBulk);
document.addEventListener("htmx:afterSwap", (evt) => {
if (evt.target.id === "tags-table-wrap") {
document.querySelectorAll('input[name="selected_tags[]"]').forEach((cb) => {
cb.addEventListener("change", tagsUpdateBulk);
});
tagsUpdateBulk();
}
+16 -15
View File
@@ -14,15 +14,15 @@
window.__adminTocBuilt = true;
function build() {
var main = document.getElementById('main-content');
var toc = document.getElementById('admin-toc');
var list = document.getElementById('admin-toc-list');
var main = document.getElementById("main-content");
var toc = document.getElementById("admin-toc");
var list = document.getElementById("admin-toc-list");
if (!main || !toc || !list) return;
// Guard against double population
if (list.children.length > 0) return;
var sections = main.querySelectorAll('section[aria-labelledby]');
var sections = main.querySelectorAll("section[aria-labelledby]");
if (sections.length < 2) {
toc.hidden = true;
return;
@@ -30,16 +30,16 @@
var items = [];
sections.forEach((sec) => {
var headingId = sec.getAttribute('aria-labelledby');
var headingId = sec.getAttribute("aria-labelledby");
var heading = document.getElementById(headingId);
if (!heading) return;
if (!sec.id) sec.id = headingId;
var a = document.createElement('a');
a.href = '#' + sec.id;
var a = document.createElement("a");
a.href = `#${sec.id}`;
a.textContent = heading.textContent.trim();
var li = document.createElement('li');
var li = document.createElement("li");
li.appendChild(a);
list.appendChild(li);
items.push({ section: sec, link: a });
@@ -64,7 +64,7 @@
// Pass 1: sections whose top is above/at the line
items.forEach((item) => {
var top = item.section.getBoundingClientRect().top;
if (top <= line && (line - top) < bestDist) {
if (top <= line && line - top < bestDist) {
bestDist = line - top;
best = item;
}
@@ -88,8 +88,9 @@
var lastItem = items[items.length - 1];
if (lastItem !== best) {
var lastTop = lastItem.section.getBoundingClientRect().top;
var atBottom = (window.innerHeight + window.scrollY + 5)
>= document.documentElement.scrollHeight;
var atBottom =
window.innerHeight + window.scrollY + 5 >=
document.documentElement.scrollHeight;
if (atBottom && lastTop > line && lastTop < window.innerHeight) {
best = lastItem;
}
@@ -98,10 +99,10 @@
if (!best && items.length > 0) best = items[0];
items.forEach((item) => {
item.link.classList.toggle('toc-active', item === best);
item.link.classList.toggle("toc-active", item === best);
});
},
{ threshold: buildThresholds() }
{ threshold: buildThresholds() },
);
items.forEach((item) => {
@@ -109,8 +110,8 @@
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', build);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", build);
} else {
build();
}
@@ -78,5 +78,3 @@ document.body.addEventListener("htmx:beforeRequest", (e) => {
status.className = "autosave-status autosave-status--saving";
}
});
@@ -172,7 +172,7 @@
* Name: queue_order[<queueType>] for the pipe-separated order.
*/
function syncOrderInput(queueType, pond) {
if (!pond || !pond.element) return;
if (!pond?.element) return;
var form = pond.element.closest("form");
if (!form) return;
@@ -724,7 +724,9 @@
// Find the label for the TFE input (its parent group's <label>)
var fieldGroup = tfeInput.closest(".admin-files-fieldgroup");
var label = fieldGroup ? fieldGroup.querySelector("label[for='tfe-files-input']") : null;
var label = fieldGroup
? fieldGroup.querySelector("label[for='tfe-files-input']")
: null;
if (checkedAny) {
tfeInput.removeAttribute("required");
@@ -992,12 +994,12 @@
? data.error
: typeof data === "string"
? data
: "Erreur " + status;
: `Erreur ${status}`;
if (bodyEl)
bodyEl.innerHTML = '<p class="file-browser-error">Erreur : ' + msg + '</p>';
bodyEl.innerHTML = `<p class="file-browser-error">Erreur : ${msg}</p>`;
return;
}
console.log("[pt-relink] success | new_id=" + data.id);
console.log(`[pt-relink] success | new_id=${data.id}`);
var input = document.querySelector(
'.tfe-file-picker[data-queue-type="tfe"]',
@@ -1008,8 +1010,7 @@
var block = document.getElementById("format-fichiers-block");
if (block && window.htmx) {
var url = "/admin/fragments/fichiers.php";
if (thesisId)
url += "?_thesis_id=" + encodeURIComponent(thesisId);
if (thesisId) url += `?_thesis_id=${encodeURIComponent(thesisId)}`;
htmx.ajax("GET", url, {
target: "#format-fichiers-block",
swap: "outerHTML",
@@ -1050,8 +1051,7 @@
.catch((err) => {
console.error("[pt-relink] fetch error", err);
if (bodyEl)
bodyEl.innerHTML =
'<p class="file-browser-error">Erreur réseau.</p>';
bodyEl.innerHTML = '<p class="file-browser-error">Erreur réseau.</p>';
});
};
})();
@@ -5,22 +5,22 @@
* receives a single integer (total minutes).
*/
(() => {
var form = document.querySelector('form.admin-form');
var form = document.querySelector("form.admin-form");
if (!form) return;
form.addEventListener('submit', function() {
var h = parseInt(document.getElementById('duration_h')?.value, 10) || 0;
var m = parseInt(document.getElementById('duration_m')?.value, 10) || 0;
form.addEventListener("submit", () => {
var h = parseInt(document.getElementById("duration_h")?.value, 10) || 0;
var m = parseInt(document.getElementById("duration_m")?.value, 10) || 0;
var total = h * 60 + m;
var hidden = document.getElementById('duration_minutes_hidden');
var hidden = document.getElementById("duration_minutes_hidden");
if (!hidden) {
hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = 'duration_minutes';
hidden.id = 'duration_minutes_hidden';
hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "duration_minutes";
hidden.id = "duration_minutes_hidden";
form.appendChild(hidden);
}
hidden.value = total > 0 ? String(total) : '';
hidden.value = total > 0 ? String(total) : "";
});
})();
+22 -16
View File
@@ -9,15 +9,15 @@
window.addJuryRow = (listId, inputName, roleLabel) => {
var list = document.getElementById(listId);
if (!list) return;
var n = list.querySelectorAll('.admin-jury-entry').length + 1;
var div = document.createElement('div');
div.className = 'admin-jury-entry';
var n = list.querySelectorAll(".admin-jury-entry").length + 1;
var div = document.createElement("div");
div.className = "admin-jury-entry";
div.innerHTML =
'<input type="text" name="' +
inputName +
'" placeholder="Nom" autocomplete="off" aria-label="' +
roleLabel +
' ' +
" " +
n +
' \u2014 nom">' +
'<button type="button" class="btn btn--sm btn--ghost admin-btn-remove" onclick="removeJuryRow(this)" aria-label="Supprimer">' +
@@ -26,41 +26,47 @@
};
window.removeJuryRow = (btn) => {
var entry = btn.closest('.admin-jury-entry');
var entry = btn.closest(".admin-jury-entry");
if (entry) entry.remove();
};
// ── ULB toggle ─────────────────────────────────────────────────────────
var juryFieldset = document.querySelector('fieldset[data-admin-mode]');
var adminMode = juryFieldset ? juryFieldset.getAttribute('data-admin-mode') === '1' : false;
var juryFieldset = document.querySelector("fieldset[data-admin-mode]");
var adminMode = juryFieldset
? juryFieldset.getAttribute("data-admin-mode") === "1"
: false;
try {
var finalitySelect = document.querySelector('select[name="finality"]');
var ulbRow = document.getElementById('jury-promoteur-ulb-row');
var ulbRow = document.getElementById("jury-promoteur-ulb-row");
if (!finalitySelect || !ulbRow) return;
var ulbAsterisk = document.getElementById('jury-ulb-asterisk');
var ulbAsterisk = document.getElementById("jury-ulb-asterisk");
function isApprofondiSelected() {
var opt = finalitySelect.options[finalitySelect.selectedIndex];
if (!opt) return false;
return (opt.textContent || opt.text || '').toLowerCase().includes('approfondi');
return (opt.textContent || opt.text || "")
.toLowerCase()
.includes("approfondi");
}
function toggleUlb() {
var show = isApprofondiSelected();
ulbRow.style.display = show ? '' : 'none';
if (ulbAsterisk) ulbAsterisk.style.display = show ? '' : 'none';
var inputs = ulbRow.querySelectorAll('input[name="jury_promoteur_ulb_name[]"]');
ulbRow.style.display = show ? "" : "none";
if (ulbAsterisk) ulbAsterisk.style.display = show ? "" : "none";
var inputs = ulbRow.querySelectorAll(
'input[name="jury_promoteur_ulb_name[]"]',
);
inputs.forEach((inp, idx) => {
inp.required = adminMode ? false : show && idx === 0;
inp.disabled = !show;
if (!show) inp.value = '';
if (!show) inp.value = "";
});
}
finalitySelect.addEventListener('change', toggleUlb);
finalitySelect.addEventListener("change", toggleUlb);
toggleUlb();
} catch (e) {
console.error('jury ULB toggle:', e);
console.error("jury ULB toggle:", e);
}
})();
@@ -5,25 +5,25 @@
* Reads the container id from data-search-container-id on the pill-search div.
*/
(() => {
var pillDiv = document.querySelector('[data-search-container-id]');
var pillDiv = document.querySelector("[data-search-container-id]");
if (!pillDiv) return;
var containerId = pillDiv.getAttribute('data-search-container-id');
var containerId = pillDiv.getAttribute("data-search-container-id");
var container = document.getElementById(containerId);
if (!container) return;
var pills = container.querySelector('.tag-search-pills');
var pills = container.querySelector(".tag-search-pills");
if (!pills) return;
function check() {
var asteriskEl = document.getElementById('languages-required-asterisk');
var asteriskEl = document.getElementById("languages-required-asterisk");
if (!asteriskEl) return;
var n = pills.querySelectorAll('.tag-pill').length;
var n = pills.querySelectorAll(".tag-pill").length;
var checkboxes = document.querySelectorAll(
'#languages-fieldset input[type="checkbox"]:checked'
'#languages-fieldset input[type="checkbox"]:checked',
);
asteriskEl.innerHTML =
n === 0 && checkboxes.length === 0
? ' <span class="asterisk">*</span>'
: '';
: "";
}
var observer = new MutationObserver(check);
+12 -9
View File
@@ -9,30 +9,33 @@
*/
(() => {
// Toast accessibility — auto-focus warning toasts
document.body.addEventListener('htmx:afterSettle', (e) => {
if (e.target && e.target.id === 'toast-region') {
var warn = e.target.querySelector('.toast--warning');
document.body.addEventListener("htmx:afterSettle", (e) => {
if (e.target && e.target.id === "toast-region") {
var warn = e.target.querySelector(".toast--warning");
if (warn) {
warn.setAttribute('tabindex', '-1');
warn.setAttribute("tabindex", "-1");
warn.focus();
}
}
});
// Markdown cheatsheet: remove stale dialogs before a new one arrives
document.body.addEventListener('htmx:beforeRequest', (e) => {
document.body.addEventListener("htmx:beforeRequest", (e) => {
if (
e.detail.requestConfig &&
e.detail.requestConfig.path === '/admin/markdown-cheatsheet-fragment.php'
e.detail.requestConfig.path === "/admin/markdown-cheatsheet-fragment.php"
) {
var old = document.getElementById('md-cheatsheet-dialog');
var old = document.getElementById("md-cheatsheet-dialog");
if (old) old.remove();
}
});
// Markdown cheatsheet: close on backdrop (dialog element) click
document.body.addEventListener('click', (e) => {
if (e.target.tagName === 'DIALOG' && e.target.id === 'md-cheatsheet-dialog') {
document.body.addEventListener("click", (e) => {
if (
e.target.tagName === "DIALOG" &&
e.target.id === "md-cheatsheet-dialog"
) {
e.target.close();
}
});
@@ -5,13 +5,13 @@
* On desktop, all panels close when crossing the breakpoint.
*/
(() => {
var INDEX_SEL = '#repertoire-index';
var ACCORDION_SEL = '.rep-accordion';
var TOGGLE_SEL = '.rep-accordion__toggle';
var PANEL_SEL = '.rep-accordion__panel';
var INDEX_SEL = "#repertoire-index";
var ACCORDION_SEL = ".rep-accordion";
var TOGGLE_SEL = ".rep-accordion__toggle";
var PANEL_SEL = ".rep-accordion__panel";
function isMobile() {
return window.matchMedia('(max-width: 1025px)').matches;
return window.matchMedia("(max-width: 1025px)").matches;
}
function initAccordions(root) {
@@ -22,29 +22,29 @@
if (btn.closest('[data-col="students"]')) return;
if (btn._accordionBound) return;
btn._accordionBound = true;
btn.addEventListener('click', () => {
btn.addEventListener("click", () => {
var section = btn.closest(ACCORDION_SEL);
var panel = section.querySelector(PANEL_SEL);
var isOpen = btn.getAttribute('aria-expanded') === 'true';
var isOpen = btn.getAttribute("aria-expanded") === "true";
// Close all others (except students column)
root.querySelectorAll(ACCORDION_SEL).forEach((s) => {
if (s.dataset.col === 'students') return;
if (s.dataset.col === "students") return;
var p = s.querySelector(PANEL_SEL);
var t = s.querySelector(TOGGLE_SEL);
if (s !== section) {
t.setAttribute('aria-expanded', 'false');
p.classList.remove('is-open');
t.setAttribute("aria-expanded", "false");
p.classList.remove("is-open");
}
});
// Toggle this one
var nowOpen = !isOpen;
btn.setAttribute('aria-expanded', nowOpen ? 'true' : 'false');
btn.setAttribute("aria-expanded", nowOpen ? "true" : "false");
if (nowOpen) {
panel.classList.add('is-open');
panel.classList.add("is-open");
} else {
panel.classList.remove('is-open');
panel.classList.remove("is-open");
}
});
});
@@ -54,12 +54,8 @@
initAccordions(document);
// Re-bind after HTMX swaps (use live DOM since e.detail.target may be detached)
document.body.addEventListener('htmx:afterSwap', (e) => {
if (
e.detail.target &&
e.detail.target.matches &&
e.detail.target.matches(INDEX_SEL)
) {
document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target?.matches?.(INDEX_SEL)) {
var liveIndex = document.querySelector(INDEX_SEL);
if (liveIndex) initAccordions(liveIndex);
}
@@ -67,21 +63,19 @@
// Re-bind on resize crossing the breakpoint
var wasMobile = isMobile();
window.addEventListener('resize', () => {
window.addEventListener("resize", () => {
var nowMobile = isMobile();
if (nowMobile !== wasMobile) {
wasMobile = nowMobile;
if (!nowMobile) {
// Switching to desktop — close all panels
document
.querySelectorAll(INDEX_SEL + ' ' + TOGGLE_SEL)
.querySelectorAll(`${INDEX_SEL} ${TOGGLE_SEL}`)
.forEach((btn) => {
btn.setAttribute('aria-expanded', 'false');
btn.setAttribute("aria-expanded", "false");
});
document
.querySelectorAll(INDEX_SEL + ' ' + PANEL_SEL)
.forEach((p) => {
p.classList.remove('is-open');
document.querySelectorAll(`${INDEX_SEL} ${PANEL_SEL}`).forEach((p) => {
p.classList.remove("is-open");
});
}
}
@@ -8,17 +8,17 @@
* DOM replacement.
*/
(() => {
var INDEX_SEL = '#repertoire-index';
var INDEX_SEL = "#repertoire-index";
var scrollSnapshots = {};
function snapshot() {
var index = document.querySelector(INDEX_SEL);
if (!index) return;
scrollSnapshots = {};
index.querySelectorAll('.repertoire-col[data-col] > ul').forEach((ul) => {
var col = ul.closest('.repertoire-col');
index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
var col = ul.closest(".repertoire-col");
if (!col) return;
var key = col.getAttribute('data-col');
var key = col.getAttribute("data-col");
scrollSnapshots[key] = ul.scrollTop;
});
}
@@ -26,10 +26,10 @@
function restore() {
var index = document.querySelector(INDEX_SEL);
if (!index) return;
index.querySelectorAll('.repertoire-col[data-col] > ul').forEach((ul) => {
var col = ul.closest('.repertoire-col');
index.querySelectorAll(".repertoire-col[data-col] > ul").forEach((ul) => {
var col = ul.closest(".repertoire-col");
if (!col) return;
var key = col.getAttribute('data-col');
var key = col.getAttribute("data-col");
if (scrollSnapshots[key] !== undefined) {
ul.scrollTop = scrollSnapshots[key];
}
@@ -37,23 +37,15 @@
}
// Capture scroll positions before the outgoing element is replaced
document.body.addEventListener('htmx:beforeSwap', (e) => {
if (
e.detail.target &&
e.detail.target.matches &&
e.detail.target.matches(INDEX_SEL)
) {
document.body.addEventListener("htmx:beforeSwap", (e) => {
if (e.detail.target?.matches?.(INDEX_SEL)) {
snapshot();
}
});
// Restore after the new content is in the DOM
document.body.addEventListener('htmx:afterSwap', (e) => {
if (
e.detail.target &&
e.detail.target.matches &&
e.detail.target.matches(INDEX_SEL)
) {
document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target?.matches?.(INDEX_SEL)) {
// Use requestAnimationFrame to ensure layout has settled
requestAnimationFrame(() => {
restore();
@@ -62,7 +54,7 @@
});
// Also restore on history navigation (browser back/forward)
document.body.addEventListener('htmx:historyRestore', () => {
document.body.addEventListener("htmx:historyRestore", () => {
requestAnimationFrame(() => {
restore();
});
@@ -5,7 +5,7 @@
* with `data-student-name` attribute.
*/
(() => {
var popover = document.getElementById('student-popover');
var popover = document.getElementById("student-popover");
var currentAnchor = null;
function position(anchor) {
@@ -15,43 +15,43 @@
if (left + 300 > window.innerWidth + window.scrollX) {
left = r.left + window.scrollX - 312;
}
popover.style.left = left + 'px';
popover.style.top = top + 'px';
popover.style.left = `${left}px`;
popover.style.top = `${top}px`;
}
document.body.addEventListener(
'mouseenter',
"mouseenter",
(e) => {
var a = e.target.closest('[data-student-name]');
var a = e.target.closest("[data-student-name]");
if (!a) return;
currentAnchor = a;
},
true
true,
);
document.body.addEventListener('htmx:afterSwap', (e) => {
document.body.addEventListener("htmx:afterSwap", (e) => {
if (e.detail.target !== popover) return;
if (currentAnchor) position(currentAnchor);
popover.hidden = false;
});
document.body.addEventListener(
'mouseleave',
"mouseleave",
(e) => {
if (
!e.target.closest('[data-student-name]') &&
!e.target.closest('#student-popover')
!e.target.closest("[data-student-name]") &&
!e.target.closest("#student-popover")
)
return;
setTimeout(() => {
if (
!document.querySelector('[data-student-name]:hover') &&
!document.querySelector('#student-popover:hover')
!document.querySelector("[data-student-name]:hover") &&
!document.querySelector("#student-popover:hover")
) {
popover.hidden = true;
}
}, 120);
},
true
true,
);
})();
@@ -4,42 +4,45 @@
* Operates on #sidebar-links-form and #sidebar-link-tpl.
*/
(() => {
var form = document.getElementById('sidebar-links-form');
var tpl = document.getElementById('sidebar-link-tpl');
var form = document.getElementById("sidebar-links-form");
var tpl = document.getElementById("sidebar-link-tpl");
if (!form || !tpl) return;
var tplHtml = tpl.innerHTML;
function reindexLinks() {
var rows = form.querySelectorAll('.sidebar-link-row');
var rows = form.querySelectorAll(".sidebar-link-row");
rows.forEach((row, i) => {
row.querySelectorAll('input').forEach((inp) => {
row.querySelectorAll("input").forEach((inp) => {
if (inp.name) {
inp.name = inp.name.replace(/links\[\d+\]/, 'links[' + i + ']');
inp.name = inp.name.replace(/links\[\d+\]/, `links[${i}]`);
}
if (inp.id) {
inp.id = inp.id.replace(/sl_\d+/, 'sl_' + i);
inp.id = inp.id.replace(/sl_\d+/, `sl_${i}`);
}
});
row.querySelectorAll('label[for]').forEach((lbl) => {
lbl.setAttribute('for', lbl.getAttribute('for').replace(/sl_\d+/, 'sl_' + i));
row.querySelectorAll("label[for]").forEach((lbl) => {
lbl.setAttribute(
"for",
lbl.getAttribute("for").replace(/sl_\d+/, `sl_${i}`),
);
});
});
}
// Event delegation for remove buttons
form.addEventListener('click', (e) => {
if (!e.target.closest('.remove-sidebar-link-btn')) return;
form.addEventListener("click", (e) => {
if (!e.target.closest(".remove-sidebar-link-btn")) return;
e.preventDefault();
e.target.closest('.sidebar-link-row').remove();
e.target.closest(".sidebar-link-row").remove();
reindexLinks();
});
// Add button
var addBtn = document.getElementById('add-sidebar-link-btn');
addBtn.addEventListener('click', function () {
var rows = form.querySelectorAll('.sidebar-link-row');
var addBtn = document.getElementById("add-sidebar-link-btn");
addBtn.addEventListener("click", function () {
var rows = form.querySelectorAll(".sidebar-link-row");
var idx = rows.length;
var html = tplHtml.split('{{li}}').join(idx);
this.insertAdjacentHTML('beforebegin', html);
var html = tplHtml.split("{{li}}").join(idx);
this.insertAdjacentHTML("beforebegin", html);
});
})();
+3 -3
View File
@@ -3,11 +3,11 @@
* error. Reads the field id from data-smtp-error-field on the SMTP form.
*/
(() => {
var form = document.querySelector('form[data-smtp-error-field]');
var form = document.querySelector("form[data-smtp-error-field]");
if (!form) return;
var fieldId = form.getAttribute('data-smtp-error-field');
var fieldId = form.getAttribute("data-smtp-error-field");
var el = fieldId ? document.getElementById(fieldId) : null;
if (!el) return;
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.scrollIntoView({ behavior: "smooth", block: "center" });
el.focus();
})();
-3
View File
@@ -47,9 +47,6 @@ class FormBootstrap
if (is_array($formData[$key])) {
return $formData[$key];
}
if ($formData[$key] === null) {
return $default;
}
return htmlspecialchars((string)$formData[$key]);
};
}
+7 -1
View File
@@ -24,7 +24,13 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true
"recommended": true,
"style": {
"noDescendingSpecificity": "off"
},
"correctness": {
"noInnerDeclarations": "off"
}
}
},
"formatter": {
+1 -1
View File
@@ -470,7 +470,7 @@ lint:
[group('test')]
fix:
# Auto-fix: biome (JS/CSS formatting + lint) + php-cs-fixer (PHP coding standards)
@npx biome check --write app/public/assets/css/ app/public/assets/js/app/ scripts/
@npx biome check --write --unsafe app/public/assets/css/ app/public/assets/js/app/ scripts/
@vendor/bin/php-cs-fixer fix --no-interaction
# ============================================================================
+12
View File
@@ -6,6 +6,18 @@ parameters:
count: 1
path: app/src/Controllers/SearchController.php
-
message: '#^Offset ''duration_minutes'' on array\{\}\|array\{license_id\: int\|null, access_type_id\: int\|null, context_note\: string, contact_visible\: int\|null\} on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/src/Controllers/ThesisEditController.php
-
message: '#^Offset ''duration_pages'' on array\{\}\|array\{license_id\: int\|null, access_type_id\: int\|null, context_note\: string, contact_visible\: int\|null\} on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/src/Controllers/ThesisEditController.php
-
message: '#^Property Dispatcher\:\:\$queryParams is never read, only written\.$#'
identifier: property.onlyWritten
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/**
* Build CSS bundles with Lightning CSS.
*
@@ -13,10 +14,10 @@
* Output: app/public/assets/dist/
*/
import { bundleAsync } from "lightningcss";
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { resolve, dirname, basename } from "node:path";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { bundleAsync } from "lightningcss";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/**
* Build JS bundles: one self-contained file per entry point.
*
@@ -6,10 +7,10 @@
* Output: app/public/assets/dist/{admin,public,form,partage}.min.js
*/
import { rolldown } from "rolldown";
import { resolve, dirname } from "node:path";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { writeFileSync, mkdirSync } from "node:fs";
import { rolldown } from "rolldown";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
+1 -1
View File
@@ -12,7 +12,7 @@
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
+2 -2
View File
@@ -6,8 +6,8 @@
* Staleness: any source file newer than the oldest dist file.
*/
import { statSync, existsSync, readdirSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { existsSync, readdirSync, statSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));