`;
- return;
- }
- console.log('[relink] success | new_id=' + data.id);
-
- // Add the new file to the FilePond pool, then close the modal.
- // If the DOM was replaced (e.g. live-reload), refresh the
- // form fragment via HTMX so the server re-renders the pools
- // with the newly-linked file included.
- var input = document.querySelector(`.tfe-file-picker[data-queue-type="${queueType}"]`);
- console.log('[relink] looking for input | selector=' + `.tfe-file-picker[data-queue-type="${queueType}"]` + ' | found=' + !!input);
- var closeAndRefresh = function() {
- var modal = document.getElementById('relink-modal');
- if (modal) modal.close();
- // Re-fetch the fichiers fragment from the server so the
- // newly-linked file appears in the FilePond pools.
- var block = document.getElementById('format-fichiers-block');
- if (block && window.htmx) {
- var url = '/admin/fragments/fichiers.php';
- if (window.__xamxamRelinkCtx && window.__xamxamRelinkCtx.thesisId) {
- url += '?_thesis_id=' + encodeURIComponent(window.__xamxamRelinkCtx.thesisId);
- }
- htmx.ajax('GET', url, {
- target: '#format-fichiers-block',
- swap: 'outerHTML'
- });
+ .then((r) =>
+ r.json().then((data) => ({ ok: r.ok, status: r.status, data })),
+ )
+ .then(({ ok, status, data }) => {
+ if (!ok || (data && data.ok === false)) {
+ const msg = data?.error
+ ? data.error
+ : typeof data === "string"
+ ? data
+ : `Erreur ${status}`;
+ if (bodyEl)
+ bodyEl.innerHTML = `
Erreur : ${msg}
`;
+ return;
}
- };
- if (input) {
- var pond = FilePond.find(input);
- console.log('[relink] looking for pond | found=' + !!pond);
- if (pond) {
- pond.addFile(String(data.id), {
- type: 'limbo',
- file: {
- name: fileName,
- size: fileSize,
- type: mimeType
+ console.log(`[relink] success | new_id=${data.id}`);
+
+ // Add the new file to the FilePond pool, then close the modal.
+ // If the DOM was replaced (e.g. live-reload), refresh the
+ // form fragment via HTMX so the server re-renders the pools
+ // with the newly-linked file included.
+ var input = document.querySelector(
+ `.tfe-file-picker[data-queue-type="${queueType}"]`,
+ );
+ console.log(
+ "[relink] looking for input | selector=" +
+ `.tfe-file-picker[data-queue-type="${queueType}"]` +
+ " | found=" +
+ !!input,
+ );
+ var closeAndRefresh = () => {
+ var modal = document.getElementById("relink-modal");
+ if (modal) modal.close();
+ // Re-fetch the fichiers fragment from the server so the
+ // newly-linked file appears in the FilePond pools.
+ var block = document.getElementById("format-fichiers-block");
+ if (block && window.htmx) {
+ let url = "/admin/fragments/fichiers.php";
+ if (window.__xamxamRelinkCtx?.thesisId) {
+ url +=
+ "?_thesis_id=" +
+ encodeURIComponent(window.__xamxamRelinkCtx.thesisId);
}
- }).then(function() {
- console.log('[relink] addFile resolved | source=' + String(data.id) + ' | queueType=' + queueType);
+ htmx.ajax("GET", url, {
+ target: "#format-fichiers-block",
+ swap: "outerHTML",
+ });
+ }
+ };
+ if (input) {
+ const pond = FilePond.find(input);
+ console.log(`[relink] looking for pond | found=${!!pond}`);
+ if (pond) {
+ pond
+ .addFile(String(data.id), {
+ type: "limbo",
+ file: {
+ name: fileName,
+ size: fileSize,
+ type: mimeType,
+ },
+ })
+ .then(() => {
+ console.log(
+ "[relink] addFile resolved | source=" +
+ String(data.id) +
+ " | queueType=" +
+ queueType,
+ );
+ closeAndRefresh();
+ })
+ .catch((err) => {
+ console.error("[relink] addFile rejected", err);
+ closeAndRefresh();
+ });
+ } else {
+ console.error(
+ "[relink] FilePond.find returned null for input",
+ input,
+ );
closeAndRefresh();
- }).catch(function(err) {
- console.error('[relink] addFile rejected', err);
- closeAndRefresh();
- });
+ }
} else {
- console.error('[relink] FilePond.find returned null for input', input);
+ console.warn(
+ "[relink] input not found, page may have reloaded | queueType=" +
+ queueType,
+ );
closeAndRefresh();
}
- } else {
- console.warn('[relink] input not found, page may have reloaded | queueType=' + queueType);
- closeAndRefresh();
- }
- // Mark form dirty
- window.__xamxamDirty = true;
- })
- .catch(err => {
- console.error('[relink] fetch error', err);
- if (bodyEl) bodyEl.innerHTML = '
Erreur réseau.
';
- });
+ // Mark form dirty
+ window.__xamxamDirty = true;
+ })
+ .catch((err) => {
+ console.error("[relink] fetch error", err);
+ if (bodyEl)
+ bodyEl.innerHTML = '
Erreur réseau.
';
+ });
};
})();
diff --git a/app/public/assets/js/app/jury-autocomplete.js b/app/public/assets/js/app/jury-autocomplete.js
index 0b78271..dd13590 100644
--- a/app/public/assets/js/app/jury-autocomplete.js
+++ b/app/public/assets/js/app/jury-autocomplete.js
@@ -11,130 +11,142 @@
* data-jury-hx-post — HTMX endpoint URL (required)
* data-jury-hx-target — CSS selector for the shared dropdown (optional)
*/
-(function () {
- 'use strict';
+(() => {
+ function initAll() {
+ document
+ .querySelectorAll(
+ "[data-jury-autocomplete]:not([data-jury-autocomplete-initialized])",
+ )
+ .forEach((fieldset) => {
+ fieldset.setAttribute("data-jury-autocomplete-initialized", "1");
+ initFieldset(fieldset);
+ });
+ }
- function initAll() {
- document.querySelectorAll('[data-jury-autocomplete]:not([data-jury-autocomplete-initialized])').forEach(function (fieldset) {
- fieldset.setAttribute('data-jury-autocomplete-initialized', '1');
- initFieldset(fieldset);
- });
- }
+ document.addEventListener("DOMContentLoaded", initAll);
+ document.body.addEventListener("htmx:afterSwap", initAll);
- document.addEventListener('DOMContentLoaded', initAll);
- document.body.addEventListener('htmx:afterSwap', initAll);
+ function initFieldset(fieldset) {
+ var list;
+ var activeInput;
+ var selectedIdx;
+ var debounceTimer;
- function initFieldset(fieldset) {
- var hxPost = fieldset.getAttribute('data-jury-hx-post') || '/admin/fragments/pill-search.php';
- var role = fieldset.getAttribute('data-jury-role') || '';
- var dropdown = fieldset.querySelector('.jury-suggestions');
- if (!dropdown) {
- dropdown = document.createElement('div');
- dropdown.className = 'jury-suggestions tag-search-suggestions';
- dropdown.setAttribute('role', 'listbox');
- // Insert after the list container
- var list = fieldset.querySelector('.admin-jury-list');
- if (list) {
- list.insertAdjacentElement('afterend', dropdown);
- } else {
- fieldset.appendChild(dropdown);
- }
- }
+ var hxPost =
+ fieldset.getAttribute("data-jury-hx-post") ||
+ "/admin/fragments/pill-search.php";
+ var role = fieldset.getAttribute("data-jury-role") || "";
+ var dropdown = fieldset.querySelector(".jury-suggestions");
+ if (!dropdown) {
+ dropdown = document.createElement("div");
+ dropdown.className = "jury-suggestions tag-search-suggestions";
+ dropdown.setAttribute("role", "listbox");
+ // Insert after the list container
+ list = fieldset.querySelector(".admin-jury-list");
+ if (list) {
+ list.insertAdjacentElement("afterend", dropdown);
+ } else {
+ fieldset.appendChild(dropdown);
+ }
+ }
- var activeInput = null;
- var selectedIdx = -1;
- var debounceTimer = null;
+ // Click on suggestion → fill the active input
+ dropdown.addEventListener("click", (e) => {
+ var btn = e.target.closest(".tag-search-item");
+ if (!btn) return;
+ var name = (btn.getAttribute("data-tag-name") || "").trim();
+ if (!name || !activeInput) return;
+ activeInput.value = btn.classList.contains("tag-search-item--create")
+ ? activeInput.value.trim()
+ : name;
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ activeInput.focus();
+ });
- // Click on suggestion → fill the active input
- dropdown.addEventListener('click', function (e) {
- var btn = e.target.closest('.tag-search-item');
- if (!btn) return;
- var name = (btn.getAttribute('data-tag-name') || '').trim();
- if (!name || !activeInput) return;
- activeInput.value = btn.classList.contains('tag-search-item--create')
- ? activeInput.value.trim()
- : name;
- dropdown.innerHTML = '';
- selectedIdx = -1;
- activeInput.focus();
- });
+ // Highlighting helper
+ function highlight(idx) {
+ var items = dropdown.querySelectorAll(".tag-search-item");
+ for (let i = 0; i < items.length; i++) {
+ items[i].classList.toggle("tag-search-item--highlight", i === idx);
+ }
+ }
- // Highlighting helper
- function highlight(idx) {
- var items = dropdown.querySelectorAll('.tag-search-item');
- for (var i = 0; i < items.length; i++) {
- items[i].classList.toggle('tag-search-item--highlight', i === idx);
- }
- }
+ fieldset.addEventListener("input", (e) => {
+ var inp = e.target.closest('input[type="text"]');
+ if (!inp) return;
- fieldset.addEventListener('input', function (e) {
- var inp = e.target.closest('input[type="text"]');
- if (!inp) return;
+ activeInput = inp;
+ var q = inp.value.trim();
- activeInput = inp;
- var q = inp.value.trim();
+ // Build the hx-include query — include hidden type=supervisor
+ var _typeInput = fieldset.querySelector(
+ 'input[name="type"][value="supervisor"]',
+ );
- // Build the hx-include query — include hidden type=supervisor
- var typeInput = fieldset.querySelector('input[name="type"][value="supervisor"]');
- var includeSelector = typeInput ? '[name="type"][value="supervisor"]' : '';
+ if (debounceTimer) clearTimeout(debounceTimer);
+ debounceTimer = setTimeout(() => {
+ if (q === "") {
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ return;
+ }
- if (debounceTimer) clearTimeout(debounceTimer);
- debounceTimer = setTimeout(function () {
- if (q === '') {
- dropdown.innerHTML = '';
- selectedIdx = -1;
- return;
- }
+ // Manual HTMX POST
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", hxPost);
+ xhr.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded",
+ );
+ xhr.setRequestHeader("HX-Request", "true");
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ dropdown.innerHTML = xhr.responseText;
+ selectedIdx = -1;
+ }
+ };
+ var params =
+ "type=supervisor&q=" +
+ encodeURIComponent(q) +
+ (role ? `&role=${encodeURIComponent(role)}` : "");
+ xhr.send(params);
+ }, 200);
+ });
- // Manual HTMX POST
- var xhr = new XMLHttpRequest();
- xhr.open('POST', hxPost);
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.setRequestHeader('HX-Request', 'true');
- xhr.onload = function () {
- if (xhr.status === 200) {
- dropdown.innerHTML = xhr.responseText;
- selectedIdx = -1;
- }
- };
- var params = 'type=supervisor&q=' + encodeURIComponent(q) + (role ? '&role=' + encodeURIComponent(role) : '');
- xhr.send(params);
- }, 200);
- });
+ // Keyboard navigation
+ fieldset.addEventListener("keydown", (e) => {
+ var items = dropdown.querySelectorAll(".tag-search-item");
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
+ if (items.length === 0) return;
+ e.preventDefault();
+ if (e.key === "ArrowDown") {
+ selectedIdx = (selectedIdx + 1) % items.length;
+ } else {
+ selectedIdx = selectedIdx <= 0 ? items.length - 1 : selectedIdx - 1;
+ }
+ highlight(selectedIdx);
+ } else if (e.key === "Enter") {
+ if (items.length > 0 && dropdown.innerHTML !== "") {
+ e.preventDefault();
+ if (selectedIdx >= 0 && selectedIdx < items.length) {
+ items[selectedIdx].click();
+ } else {
+ items[0].click();
+ }
+ }
+ } else if (e.key === "Escape") {
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ }
+ });
- // Keyboard navigation
- fieldset.addEventListener('keydown', function (e) {
- var items = dropdown.querySelectorAll('.tag-search-item');
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
- if (items.length === 0) return;
- e.preventDefault();
- if (e.key === 'ArrowDown') {
- selectedIdx = (selectedIdx + 1) % items.length;
- } else {
- selectedIdx = selectedIdx <= 0 ? items.length - 1 : selectedIdx - 1;
- }
- highlight(selectedIdx);
- } else if (e.key === 'Enter') {
- if (items.length > 0 && dropdown.innerHTML !== '') {
- e.preventDefault();
- if (selectedIdx >= 0 && selectedIdx < items.length) {
- items[selectedIdx].click();
- } else {
- items[0].click();
- }
- }
- } else if (e.key === 'Escape') {
- dropdown.innerHTML = '';
- selectedIdx = -1;
- }
- });
-
- // Close dropdown on outside click
- document.addEventListener('click', function (e) {
- if (!fieldset.contains(e.target)) {
- dropdown.innerHTML = '';
- selectedIdx = -1;
- }
- });
- }
+ // Close dropdown on outside click
+ document.addEventListener("click", (e) => {
+ if (!fieldset.contains(e.target)) {
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ }
+ });
+ }
})();
diff --git a/app/public/assets/js/app/pill-search.js b/app/public/assets/js/app/pill-search.js
index a116487..ddccdcd 100644
--- a/app/public/assets/js/app/pill-search.js
+++ b/app/public/assets/js/app/pill-search.js
@@ -20,152 +20,172 @@
* data-pill-required → si "1", active l'affichage du minimum
* data-pill-role → "tag" (lowercase) ou "lang" (ucfirst)
*/
-(function () {
- 'use strict';
+(() => {
+ function initAll() {
+ document
+ .querySelectorAll(
+ "[data-pill-search]:not([data-pill-search-initialized])",
+ )
+ .forEach((container) => {
+ container.setAttribute("data-pill-search-initialized", "1");
+ initPillSearch(container);
+ });
+ }
- function initAll() {
- document.querySelectorAll('[data-pill-search]:not([data-pill-search-initialized])').forEach(function (container) {
- container.setAttribute('data-pill-search-initialized', '1');
- initPillSearch(container);
- });
- }
+ document.addEventListener("DOMContentLoaded", initAll);
+ document.body.addEventListener("htmx:afterSwap", initAll);
- document.addEventListener('DOMContentLoaded', initAll);
- document.body.addEventListener('htmx:afterSwap', initAll);
+ function initPillSearch(container) {
+ var pills = container.querySelector(".tag-search-pills");
+ var search = container.querySelector(".tag-search-input");
+ var dropdown = container.querySelector(".tag-search-suggestions");
+ var countEl = container.querySelector(".tag-search-count");
+ var counter = container.querySelector(".tag-search-counter");
+ var maxTags = parseInt(container.getAttribute("data-pill-max"), 10) || 10;
+ var minTags = parseInt(container.getAttribute("data-pill-min"), 10) || 0;
+ var required = container.getAttribute("data-pill-required") === "1";
+ var inputName = container.getAttribute("data-pill-name") || "tag";
+ var _role = container.getAttribute("data-pill-role") || "tag";
+ var selectedIdx = -1;
- function initPillSearch(container) {
- var pills = container.querySelector('.tag-search-pills');
- var search = container.querySelector('.tag-search-input');
- var dropdown = container.querySelector('.tag-search-suggestions');
- var countEl = container.querySelector('.tag-search-count');
- var counter = container.querySelector('.tag-search-counter');
- var maxTags = parseInt(container.getAttribute('data-pill-max')) || 10;
- var minTags = parseInt(container.getAttribute('data-pill-min')) || 0;
- var required = container.getAttribute('data-pill-required') === '1';
- var inputName = container.getAttribute('data-pill-name') || 'tag';
- var role = container.getAttribute('data-pill-role') || 'tag';
- var selectedIdx = -1;
+ if (!pills || !search || !dropdown) return;
- if (!pills || !search || !dropdown) return;
+ function normalize(name) {
+ return name.trim().replace(/\s+/g, " ").toLowerCase();
+ }
- function normalize(name) {
- return name.trim().replace(/\s+/g, ' ').toLowerCase();
- }
+ function pillAlreadyExists(name) {
+ var norm = normalize(name);
+ var existing = pills.querySelectorAll(".tag-pill-name");
+ for (let i = 0; i < existing.length; i++) {
+ if (normalize(existing[i].textContent) === norm) return true;
+ }
+ return false;
+ }
- function pillAlreadyExists(name) {
- var norm = normalize(name);
- var existing = pills.querySelectorAll('.tag-pill-name');
- for (var i = 0; i < existing.length; i++) {
- if (normalize(existing[i].textContent) === norm) return true;
- }
- return false;
- }
+ function updateCount() {
+ var n = pills.querySelectorAll(".tag-pill").length;
+ var suffix = required ? ` (min ${minTags})` : "";
+ if (countEl) countEl.textContent = `${n}/${maxTags}${suffix}`;
+ if (counter) counter.style.display = n > 0 || required ? "" : "none";
+ if (countEl && required) {
+ countEl.style.color =
+ n < minTags ? "var(--text-danger)" : "var(--accent)";
+ }
- function updateCount() {
- var n = pills.querySelectorAll('.tag-pill').length;
- var suffix = required ? ' (min ' + minTags + ')' : '';
- if (countEl) countEl.textContent = n + '/' + maxTags + suffix;
- if (counter) counter.style.display = (n > 0 || required) ? '' : 'none';
- if (countEl && required) {
- countEl.style.color = n < minTags ? 'var(--text-danger)' : 'var(--accent)';
- }
+ var wrap = container.querySelector(".tag-search-input-wrap");
+ var maxMsg = container.querySelector(".tag-search-max-msg");
+ if (n >= maxTags) {
+ if (wrap) wrap.style.display = "none";
+ if (maxMsg) maxMsg.style.display = "";
+ } else {
+ if (wrap) {
+ wrap.style.display = "";
+ if (search) search.style.display = "";
+ }
+ if (maxMsg) maxMsg.style.display = "none";
+ }
+ }
- var wrap = container.querySelector('.tag-search-input-wrap');
- var maxMsg = container.querySelector('.tag-search-max-msg');
- if (n >= maxTags) {
- if (wrap) wrap.style.display = 'none';
- if (maxMsg) maxMsg.style.display = '';
- } else {
- if (wrap) { wrap.style.display = ''; if (search) search.style.display = ''; }
- if (maxMsg) maxMsg.style.display = 'none';
- }
- }
+ pills.addEventListener("click", (e) => {
+ var btn = e.target.closest(".tag-pill-remove");
+ if (!btn) return;
+ var pill = btn.closest(".tag-pill");
+ pill.remove();
+ updateCount();
+ var wrap = container.querySelector(".tag-search-input-wrap");
+ var inp = container.querySelector(".tag-search-input");
+ if (wrap && inp) {
+ wrap.style.display = "";
+ inp.style.display = "";
+ }
+ });
- pills.addEventListener('click', function (e) {
- var btn = e.target.closest('.tag-pill-remove');
- if (!btn) return;
- var pill = btn.closest('.tag-pill');
- pill.remove();
- updateCount();
- var wrap = container.querySelector('.tag-search-input-wrap');
- var inp = container.querySelector('.tag-search-input');
- if (wrap && inp) { wrap.style.display = ''; inp.style.display = ''; }
- });
+ function highlight(idx) {
+ var items = dropdown.querySelectorAll(".tag-search-item");
+ for (let i = 0; i < items.length; i++) {
+ items[i].classList.toggle("tag-search-item--highlight", i === idx);
+ }
+ }
- function highlight(idx) {
- var items = dropdown.querySelectorAll('.tag-search-item');
- for (var i = 0; i < items.length; i++) {
- items[i].classList.toggle('tag-search-item--highlight', i === idx);
- }
- }
+ function selectPill(btn) {
+ var name = normalize(btn.getAttribute("data-tag-name") || "");
+ if (!name) return;
+ if (pillAlreadyExists(name)) return;
+ if (pills.querySelectorAll(".tag-pill").length >= maxTags) return;
- function selectPill(btn) {
- var name = normalize(btn.getAttribute('data-tag-name') || '');
- if (!name) return;
- if (pillAlreadyExists(name)) return;
- if ((pills.querySelectorAll('.tag-pill').length) >= maxTags) return;
+ var escaped = htmlEscape(name);
+ var pill = document.createElement("span");
+ pill.className = "tag-pill";
+ pill.innerHTML =
+ '' +
+ '' +
+ escaped +
+ "" +
+ '";
+ pills.appendChild(pill);
+ updateCount();
+ search.value = "";
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ search.focus();
+ }
- var escaped = htmlEscape(name);
- var pill = document.createElement('span');
- pill.className = 'tag-pill';
- pill.innerHTML = ''
- + '' + escaped + ''
- + '';
- pills.appendChild(pill);
- updateCount();
- search.value = '';
- dropdown.innerHTML = '';
- selectedIdx = -1;
- search.focus();
- }
+ dropdown.addEventListener("click", (e) => {
+ var btn = e.target.closest(".tag-search-item");
+ if (!btn) return;
+ selectPill(btn);
+ });
- dropdown.addEventListener('click', function (e) {
- var btn = e.target.closest('.tag-search-item');
- if (!btn) return;
- selectPill(btn);
- });
+ search.addEventListener("keydown", (e) => {
+ var items = dropdown.querySelectorAll(".tag-search-item");
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
+ e.preventDefault();
+ if (items.length === 0) return;
+ if (e.key === "ArrowDown") {
+ selectedIdx = (selectedIdx + 1) % items.length;
+ } else {
+ selectedIdx = selectedIdx <= 0 ? items.length - 1 : selectedIdx - 1;
+ }
+ highlight(selectedIdx);
+ } else if (e.key === "Enter") {
+ if (items.length > 0) {
+ e.preventDefault();
+ if (selectedIdx >= 0 && selectedIdx < items.length) {
+ selectPill(items[selectedIdx]);
+ } else {
+ selectPill(items[0]);
+ }
+ }
+ } else if (e.key === "Escape") {
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ }
+ });
- search.addEventListener('keydown', function (e) {
- var items = dropdown.querySelectorAll('.tag-search-item');
- if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
- e.preventDefault();
- if (items.length === 0) return;
- if (e.key === 'ArrowDown') {
- selectedIdx = (selectedIdx + 1) % items.length;
- } else {
- selectedIdx = selectedIdx <= 0 ? items.length - 1 : selectedIdx - 1;
- }
- highlight(selectedIdx);
- } else if (e.key === 'Enter') {
- if (items.length > 0) {
- e.preventDefault();
- if (selectedIdx >= 0 && selectedIdx < items.length) {
- selectPill(items[selectedIdx]);
- } else {
- selectPill(items[0]);
- }
- }
- } else if (e.key === 'Escape') {
- dropdown.innerHTML = '';
- selectedIdx = -1;
- }
- });
+ search.addEventListener("blur", () => {
+ setTimeout(() => {
+ if (!dropdown.contains(document.activeElement)) {
+ dropdown.innerHTML = "";
+ selectedIdx = -1;
+ }
+ }, 150);
+ });
- search.addEventListener('blur', function () {
- setTimeout(function () {
- if (!dropdown.contains(document.activeElement)) {
- dropdown.innerHTML = '';
- selectedIdx = -1;
- }
- }, 150);
- });
-
- function htmlEscape(str) {
- var el = document.createElement('span');
- el.textContent = str;
- return el.innerHTML;
- }
- }
+ function htmlEscape(str) {
+ var el = document.createElement("span");
+ el.textContent = str;
+ return el.innerHTML;
+ }
+ }
})();
diff --git a/app/public/assets/js/app/upload-progress.js b/app/public/assets/js/app/upload-progress.js
index ce04227..1488d24 100644
--- a/app/public/assets/js/app/upload-progress.js
+++ b/app/public/assets/js/app/upload-progress.js
@@ -11,203 +11,211 @@
* 100% : response received — "Téléversé avec succès", then redirect
*/
(() => {
- 'use strict';
+ const FORMS = document.querySelectorAll("form[data-upload-progress]");
+ if (!FORMS.length) return;
- const FORMS = document.querySelectorAll('form[data-upload-progress]');
- if (!FORMS.length) return;
+ const POLL_INTERVAL = 400;
+ const UPLOAD_CAP = 25;
+ const PROCESSING_MAX = 99;
+ const SUCCESS_DELAY = 800;
- const POLL_INTERVAL = 400;
- const UPLOAD_CAP = 25;
- const PROCESSING_MAX = 99;
- const SUCCESS_DELAY = 800;
+ for (const form of FORMS) {
+ const progressWrap = form.querySelector("#upload-progress-wrap");
+ const progressBar = form.querySelector("#upload-progress-bar");
+ const progressLabel = form.querySelector("#upload-progress-label");
+ const progressFile = form.querySelector("#upload-progress-file");
+ const submitBtn = form.querySelector('button[type="submit"]');
+ const tokenInput = form.querySelector('input[name="progress_token"]');
- for (const form of FORMS) {
- const progressWrap = form.querySelector('#upload-progress-wrap');
- const progressBar = form.querySelector('#upload-progress-bar');
- const progressLabel = form.querySelector('#upload-progress-label');
- const progressFile = form.querySelector('#upload-progress-file');
- const submitBtn = form.querySelector('button[type="submit"]');
- const tokenInput = form.querySelector('input[name="progress_token"]');
+ if (!progressBar || !progressWrap) continue;
- if (!progressBar || !progressWrap) continue;
+ function collectFileNames() {
+ const names = [];
+ // Check raw elements (non-FilePond)
+ const inputs = form.querySelectorAll('input[type="file"]');
+ for (const fi of inputs) {
+ if (fi.files) {
+ for (const f of fi.files) {
+ if (f.name) names.push(f.name);
+ }
+ }
+ }
+ // Read processed file names from FilePond instances (async mode)
+ if (typeof FilePond !== "undefined") {
+ const pondInputs = form.querySelectorAll(".tfe-file-picker");
+ for (const pi of pondInputs) {
+ const pond = FilePond.find(pi);
+ if (pond) {
+ const pondFiles = pond.getFiles();
+ for (const pf of pondFiles) {
+ // Only count successfully uploaded files (have serverId)
+ if (pf.serverId) {
+ const name = pf.filename || pf.file?.name || pf.serverId;
+ if (name) names.push(name);
+ }
+ }
+ }
+ }
+ }
+ return names;
+ }
- function collectFileNames() {
- const names = [];
- // Check raw elements (non-FilePond)
- const inputs = form.querySelectorAll('input[type="file"]');
- for (const fi of inputs) {
- if (fi.files) {
- for (const f of fi.files) {
- if (f.name) names.push(f.name);
- }
- }
- }
- // Read processed file names from FilePond instances (async mode)
- if (typeof FilePond !== 'undefined') {
- const pondInputs = form.querySelectorAll('.tfe-file-picker');
- for (const pi of pondInputs) {
- const pond = FilePond.find(pi);
- if (pond) {
- const pondFiles = pond.getFiles();
- for (const pf of pondFiles) {
- // Only count successfully uploaded files (have serverId)
- if (pf.serverId) {
- const name = pf.filename || (pf.file && pf.file.name) || pf.serverId;
- if (name) names.push(name);
- }
- }
- }
- }
- }
- return names;
- }
+ form.addEventListener("submit", (e) => {
+ // ── Guard: block submit if any FilePond item is still uploading ──
+ if (typeof FilePond !== "undefined") {
+ let stillUploading = false;
+ const pondInputs = form.querySelectorAll(".tfe-file-picker");
+ for (const pi of pondInputs) {
+ const pond = FilePond.find(pi);
+ if (pond) {
+ const pondFiles = pond.getFiles();
+ for (const pf of pondFiles) {
+ if (
+ pf.status === FilePond.FileStatus.PROCESSING ||
+ pf.status === FilePond.FileStatus.IDLE
+ ) {
+ stillUploading = true;
+ break;
+ }
+ }
+ }
+ if (stillUploading) break;
+ }
+ if (stillUploading) {
+ e.preventDefault();
+ progressLabel.textContent =
+ "Veuillez attendre la fin du téléversement…";
+ progressWrap.style.display = "";
+ return;
+ }
+ }
- form.addEventListener('submit', function (e) {
- // ── Guard: block submit if any FilePond item is still uploading ──
- if (typeof FilePond !== 'undefined') {
- let stillUploading = false;
- const pondInputs = form.querySelectorAll('.tfe-file-picker');
- for (const pi of pondInputs) {
- const pond = FilePond.find(pi);
- if (pond) {
- const pondFiles = pond.getFiles();
- for (const pf of pondFiles) {
- if (pf.status === FilePond.FileStatus.PROCESSING ||
- pf.status === FilePond.FileStatus.IDLE) {
- stillUploading = true;
- break;
- }
- }
- }
- if (stillUploading) break;
- }
- if (stillUploading) {
- e.preventDefault();
- progressLabel.textContent = 'Veuillez attendre la fin du téléversement…';
- progressWrap.style.display = '';
- return;
- }
- }
+ const fileNames = collectFileNames();
+ if (!fileNames.length) return;
- const fileNames = collectFileNames();
- if (!fileNames.length) return;
+ e.preventDefault();
- e.preventDefault();
+ const token = tokenInput ? tokenInput.value : "";
- const token = tokenInput ? tokenInput.value : '';
+ progressWrap.style.display = "";
+ progressBar.value = 0;
+ progressBar.removeAttribute("data-complete");
+ progressLabel.textContent = "Téléversement en cours…";
+ progressFile.textContent =
+ fileNames.length === 1 ? fileNames[0] : `${fileNames.length} fichiers`;
+ if (submitBtn) submitBtn.disabled = true;
- progressWrap.style.display = '';
- progressBar.value = 0;
- progressBar.removeAttribute('data-complete');
- progressLabel.textContent = 'Téléversement en cours…';
- progressFile.textContent = fileNames.length === 1
- ? fileNames[0]
- : fileNames.length + ' fichiers';
- if (submitBtn) submitBtn.disabled = true;
+ const fd = new FormData(form);
+ const xhr = new XMLHttpRequest();
- const fd = new FormData(form);
- const xhr = new XMLHttpRequest();
+ let _uploadDone = false;
+ let lastUploadPct = 0;
+ let pollingTimer = null;
- let uploadDone = false;
- let lastUploadPct = 0;
- let pollingTimer = null;
+ /** Poll server-side progress */
+ function startPolling() {
+ if (pollingTimer || !token) return;
+ progressLabel.textContent = "Traitement en cours…";
+ pollingTimer = setInterval(() => {
+ fetch(
+ "/admin/actions/upload-progress.php?token=" +
+ encodeURIComponent(token),
+ )
+ .then((r) => r.json())
+ .then((data) => {
+ if (data?.stage && data.stage !== "upload") {
+ const pct = Math.min(
+ PROCESSING_MAX,
+ Math.max(UPLOAD_CAP, data.pct || UPLOAD_CAP),
+ );
+ progressBar.value = pct;
+ if (data.file) {
+ progressFile.textContent = data.file;
+ }
+ }
+ })
+ .catch(() => {
+ /* ignore poll errors */
+ });
+ }, POLL_INTERVAL);
+ }
- /** Poll server-side progress */
- function startPolling() {
- if (pollingTimer || !token) return;
- progressLabel.textContent = 'Traitement en cours…';
- pollingTimer = setInterval(function () {
- fetch('/admin/actions/upload-progress.php?token=' + encodeURIComponent(token))
- .then(function (r) { return r.json(); })
- .then(function (data) {
- if (data && data.stage && data.stage !== 'upload') {
- const pct = Math.min(PROCESSING_MAX, Math.max(UPLOAD_CAP, data.pct || UPLOAD_CAP));
- progressBar.value = pct;
- if (data.file) {
- progressFile.textContent = data.file;
- }
- }
- })
- .catch(function () { /* ignore poll errors */ });
- }, POLL_INTERVAL);
- }
+ function stopPolling() {
+ if (pollingTimer) {
+ clearInterval(pollingTimer);
+ pollingTimer = null;
+ }
+ }
- function stopPolling() {
- if (pollingTimer) {
- clearInterval(pollingTimer);
- pollingTimer = null;
- }
- }
+ function finishSuccess() {
+ stopPolling();
+ progressBar.value = 100;
+ progressBar.setAttribute("data-complete", "");
+ progressLabel.textContent = "Téléversé avec succès";
+ progressFile.textContent = "";
+ }
- function finishSuccess() {
- stopPolling();
- progressBar.value = 100;
- progressBar.setAttribute('data-complete', '');
- progressLabel.textContent = 'Téléversé avec succès';
- progressFile.textContent = '';
- }
+ // ── Upload phase (0% → UPLOAD_CAP) ──
+ xhr.upload.addEventListener("progress", (evt) => {
+ if (evt.lengthComputable) {
+ const rawPct = Math.round((evt.loaded / evt.total) * 100);
+ const scaled = Math.round((rawPct / 100) * UPLOAD_CAP);
+ if (scaled > lastUploadPct) {
+ lastUploadPct = scaled;
+ progressBar.value = scaled;
+ }
+ }
+ });
- // ── Upload phase (0% → UPLOAD_CAP) ──
- xhr.upload.addEventListener('progress', function (evt) {
- if (evt.lengthComputable) {
- const rawPct = Math.round((evt.loaded / evt.total) * 100);
- const scaled = Math.round((rawPct / 100) * UPLOAD_CAP);
- if (scaled > lastUploadPct) {
- lastUploadPct = scaled;
- progressBar.value = scaled;
- }
- }
- });
+ xhr.upload.addEventListener("loadend", () => {
+ _uploadDone = true;
+ progressBar.value = UPLOAD_CAP;
+ startPolling();
+ });
- xhr.upload.addEventListener('loadend', function () {
- uploadDone = true;
- progressBar.value = UPLOAD_CAP;
- startPolling();
- });
+ // ── Response handling ──
+ xhr.addEventListener("readystatechange", () => {
+ if (xhr.readyState !== XMLHttpRequest.DONE) return;
- // ── Response handling ──
- xhr.addEventListener('readystatechange', function () {
- if (xhr.readyState !== XMLHttpRequest.DONE) return;
+ stopPolling();
- stopPolling();
+ if (xhr.status >= 200 && xhr.status < 300) {
+ finishSuccess();
- if (xhr.status >= 200 && xhr.status < 300) {
- finishSuccess();
+ setTimeout(() => {
+ const finalUrl = xhr.responseURL || "";
+ if (finalUrl && finalUrl !== form.action) {
+ window.location.href = finalUrl;
+ } else {
+ document.open();
+ document.write(xhr.responseText);
+ document.close();
+ }
+ }, SUCCESS_DELAY);
+ } else {
+ progressLabel.textContent = "Erreur";
+ progressFile.textContent = "Échec du téléversement";
+ document.open();
+ document.write(xhr.responseText);
+ document.close();
+ }
+ });
- setTimeout(function () {
- const finalUrl = xhr.responseURL || '';
- if (finalUrl && finalUrl !== form.action) {
- window.location.href = finalUrl;
- } else {
- document.open();
- document.write(xhr.responseText);
- document.close();
- }
- }, SUCCESS_DELAY);
- } else {
- progressLabel.textContent = 'Erreur';
- progressFile.textContent = 'Échec du téléversement';
- document.open();
- document.write(xhr.responseText);
- document.close();
- }
- });
+ xhr.addEventListener("error", () => {
+ stopPolling();
+ progressLabel.textContent = "Erreur réseau";
+ progressFile.textContent = "";
+ if (submitBtn) submitBtn.disabled = false;
+ });
- xhr.addEventListener('error', function () {
- stopPolling();
- progressLabel.textContent = 'Erreur réseau';
- progressFile.textContent = '';
- if (submitBtn) submitBtn.disabled = false;
- });
+ xhr.addEventListener("abort", () => {
+ stopPolling();
+ progressWrap.style.display = "none";
+ if (submitBtn) submitBtn.disabled = false;
+ });
- xhr.addEventListener('abort', function () {
- stopPolling();
- progressWrap.style.display = 'none';
- if (submitBtn) submitBtn.disabled = false;
- });
-
- xhr.open('POST', form.action, true);
- xhr.send(fd);
- });
- }
+ xhr.open("POST", form.action, true);
+ xhr.send(fd);
+ });
+ }
})();
diff --git a/app/public/assets/js/vendor/filepond-plugin-file-validate-size.min.js b/app/public/assets/js/vendor/filepond-plugin-file-validate-size.min.js
index 17fcb9d..611df58 100644
--- a/app/public/assets/js/vendor/filepond-plugin-file-validate-size.min.js
+++ b/app/public/assets/js/vendor/filepond-plugin-file-validate-size.min.js
@@ -6,4 +6,118 @@
/* eslint-disable */
-!function(e,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(e=e||self).FilePondPluginFileValidateSize=i()}(this,function(){"use strict";var e=function(e){var i=e.addFilter,E=e.utils,l=E.Type,_=E.replaceInString,n=E.toNaturalFileSize;return i("ALLOW_HOPPER_ITEM",function(e,i){var E=i.query;if(!E("GET_ALLOW_FILE_SIZE_VALIDATION"))return!0;var l=E("GET_MAX_FILE_SIZE");if(null!==l&&e.size>l)return!1;var _=E("GET_MIN_FILE_SIZE");return!(null!==_&&e.size<_)}),i("LOAD_FILE",function(e,i){var E=i.query;return new Promise(function(i,l){if(!E("GET_ALLOW_FILE_SIZE_VALIDATION"))return i(e);var I=E("GET_FILE_VALIDATE_SIZE_FILTER");if(I&&!I(e))return i(e);var t=E("GET_MAX_FILE_SIZE");if(null!==t&&e.size>t)l({status:{main:E("GET_LABEL_MAX_FILE_SIZE_EXCEEDED"),sub:_(E("GET_LABEL_MAX_FILE_SIZE"),{filesize:n(t,".",E("GET_FILE_SIZE_BASE"),E("GET_FILE_SIZE_LABELS",E))})}});else{var L=E("GET_MIN_FILE_SIZE");if(null!==L&&e.sizea)return void l({status:{main:E("GET_LABEL_MAX_TOTAL_FILE_SIZE_EXCEEDED"),sub:_(E("GET_LABEL_MAX_TOTAL_FILE_SIZE"),{filesize:n(a,".",E("GET_FILE_SIZE_BASE"),E("GET_FILE_SIZE_LABELS",E))})}});i(e)}}})}),{options:{allowFileSizeValidation:[!0,l.BOOLEAN],maxFileSize:[null,l.INT],minFileSize:[null,l.INT],maxTotalFileSize:[null,l.INT],fileValidateSizeFilter:[null,l.FUNCTION],labelMinFileSizeExceeded:["File is too small",l.STRING],labelMinFileSize:["Minimum file size is {filesize}",l.STRING],labelMaxFileSizeExceeded:["File is too large",l.STRING],labelMaxFileSize:["Maximum file size is {filesize}",l.STRING],labelMaxTotalFileSizeExceeded:["Maximum total size exceeded",l.STRING],labelMaxTotalFileSize:["Maximum total file size is {filesize}",l.STRING]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:e})),e});
+!((e, i) => {
+ "object" == typeof exports && "undefined" != typeof module
+ ? (module.exports = i())
+ : "function" == typeof define && define.amd
+ ? define(i)
+ : ((e = e || self).FilePondPluginFileValidateSize = i());
+})(this, () => {
+ var e = (e) => {
+ var i = e.addFilter,
+ E = e.utils,
+ l = E.Type,
+ _ = E.replaceInString,
+ n = E.toNaturalFileSize;
+ return (
+ i("ALLOW_HOPPER_ITEM", (e, i) => {
+ var E = i.query;
+ if (!E("GET_ALLOW_FILE_SIZE_VALIDATION")) return !0;
+ var l = E("GET_MAX_FILE_SIZE");
+ if (null !== l && e.size > l) return !1;
+ var _ = E("GET_MIN_FILE_SIZE");
+ return !(null !== _ && e.size < _);
+ }),
+ i("LOAD_FILE", (e, i) => {
+ var E = i.query;
+ return new Promise((i, l) => {
+ if (!E("GET_ALLOW_FILE_SIZE_VALIDATION")) return i(e);
+ var I = E("GET_FILE_VALIDATE_SIZE_FILTER");
+ if (I && !I(e)) return i(e);
+ var t = E("GET_MAX_FILE_SIZE");
+ if (null !== t && e.size > t)
+ l({
+ status: {
+ main: E("GET_LABEL_MAX_FILE_SIZE_EXCEEDED"),
+ sub: _(E("GET_LABEL_MAX_FILE_SIZE"), {
+ filesize: n(
+ t,
+ ".",
+ E("GET_FILE_SIZE_BASE"),
+ E("GET_FILE_SIZE_LABELS", E),
+ ),
+ }),
+ },
+ });
+ else {
+ var L = E("GET_MIN_FILE_SIZE");
+ if (null !== L && e.size < L)
+ l({
+ status: {
+ main: E("GET_LABEL_MIN_FILE_SIZE_EXCEEDED"),
+ sub: _(E("GET_LABEL_MIN_FILE_SIZE"), {
+ filesize: n(
+ L,
+ ".",
+ E("GET_FILE_SIZE_BASE"),
+ E("GET_FILE_SIZE_LABELS", E),
+ ),
+ }),
+ },
+ });
+ else {
+ var a = E("GET_MAX_TOTAL_FILE_SIZE");
+ if (null !== a)
+ if (
+ E("GET_ACTIVE_ITEMS").reduce((e, i) => e + i.fileSize, 0) > a
+ )
+ return void l({
+ status: {
+ main: E("GET_LABEL_MAX_TOTAL_FILE_SIZE_EXCEEDED"),
+ sub: _(E("GET_LABEL_MAX_TOTAL_FILE_SIZE"), {
+ filesize: n(
+ a,
+ ".",
+ E("GET_FILE_SIZE_BASE"),
+ E("GET_FILE_SIZE_LABELS", E),
+ ),
+ }),
+ },
+ });
+ i(e);
+ }
+ }
+ });
+ }),
+ {
+ options: {
+ allowFileSizeValidation: [!0, l.BOOLEAN],
+ maxFileSize: [null, l.INT],
+ minFileSize: [null, l.INT],
+ maxTotalFileSize: [null, l.INT],
+ fileValidateSizeFilter: [null, l.FUNCTION],
+ labelMinFileSizeExceeded: ["File is too small", l.STRING],
+ labelMinFileSize: ["Minimum file size is {filesize}", l.STRING],
+ labelMaxFileSizeExceeded: ["File is too large", l.STRING],
+ labelMaxFileSize: ["Maximum file size is {filesize}", l.STRING],
+ labelMaxTotalFileSizeExceeded: [
+ "Maximum total size exceeded",
+ l.STRING,
+ ],
+ labelMaxTotalFileSize: [
+ "Maximum total file size is {filesize}",
+ l.STRING,
+ ],
+ },
+ }
+ );
+ };
+ return (
+ "undefined" != typeof window &&
+ void 0 !== window.document &&
+ document.dispatchEvent(
+ new CustomEvent("FilePond:pluginloaded", { detail: e }),
+ ),
+ e
+ );
+});
diff --git a/app/public/assets/js/vendor/filepond-plugin-file-validate-type.min.js b/app/public/assets/js/vendor/filepond-plugin-file-validate-type.min.js
index f2cb360..bf463ec 100644
--- a/app/public/assets/js/vendor/filepond-plugin-file-validate-type.min.js
+++ b/app/public/assets/js/vendor/filepond-plugin-file-validate-type.min.js
@@ -6,4 +6,113 @@
/* eslint-disable */
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).FilePondPluginFileValidateType=t()}(this,function(){"use strict";var e=function(e){var t=e.addFilter,n=e.utils,i=n.Type,T=n.isString,E=n.replaceInString,l=n.guesstimateMimeType,o=n.getExtensionFromFilename,r=n.getFilenameFromURL,u=function(e,t){return e.some(function(e){return/\*$/.test(e)?(n=e,(/^[^/]+/.exec(t)||[]).pop()===n.slice(0,-2)):e===t;var n})},a=function(e,t,n){if(0===t.length)return!0;var i=function(e){var t="";if(T(e)){var n=r(e),i=o(n);i&&(t=l(i))}else t=e.type;return t}(e);return n?new Promise(function(T,E){n(e,i).then(function(e){u(t,e)?T():E()}).catch(E)}):u(t,i)};return t("SET_ATTRIBUTE_TO_OPTION_MAP",function(e){return Object.assign(e,{accept:"acceptedFileTypes"})}),t("ALLOW_HOPPER_ITEM",function(e,t){var n=t.query;return!n("GET_ALLOW_FILE_TYPE_VALIDATION")||a(e,n("GET_ACCEPTED_FILE_TYPES"))}),t("LOAD_FILE",function(e,t){var n=t.query;return new Promise(function(t,i){if(n("GET_ALLOW_FILE_TYPE_VALIDATION")){var T=n("GET_ACCEPTED_FILE_TYPES"),l=n("GET_FILE_VALIDATE_TYPE_DETECT_TYPE"),o=a(e,T,l),r=function(){var e,t=T.map((e=n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES_MAP"),function(t){return null!==e[t]&&(e[t]||t)})).filter(function(e){return!1!==e}),l=t.filter(function(e,n){return t.indexOf(e)===n});i({status:{main:n("GET_LABEL_FILE_TYPE_NOT_ALLOWED"),sub:E(n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES"),{allTypes:l.join(", "),allButLastType:l.slice(0,-1).join(", "),lastType:l[l.length-1]})}})};if("boolean"==typeof o)return o?t(e):r();o.then(function(){t(e)}).catch(r)}else t(e)})}),{options:{allowFileTypeValidation:[!0,i.BOOLEAN],acceptedFileTypes:[[],i.ARRAY],labelFileTypeNotAllowed:["File is of invalid type",i.STRING],fileValidateTypeLabelExpectedTypes:["Expects {allButLastType} or {lastType}",i.STRING],fileValidateTypeLabelExpectedTypesMap:[{},i.OBJECT],fileValidateTypeDetectType:[null,i.FUNCTION]}}};return"undefined"!=typeof window&&void 0!==window.document&&document.dispatchEvent(new CustomEvent("FilePond:pluginloaded",{detail:e})),e});
+!((e, t) => {
+ "object" == typeof exports && "undefined" != typeof module
+ ? (module.exports = t())
+ : "function" == typeof define && define.amd
+ ? define(t)
+ : ((e = e || self).FilePondPluginFileValidateType = t());
+})(this, () => {
+ var e = (e) => {
+ var t = e.addFilter,
+ n = e.utils,
+ i = n.Type,
+ T = n.isString,
+ E = n.replaceInString,
+ l = n.guesstimateMimeType,
+ o = n.getExtensionFromFilename,
+ r = n.getFilenameFromURL,
+ u = (e, t) =>
+ e.some((e) =>
+ /\*$/.test(e)
+ ? ((n = e), (/^[^/]+/.exec(t) || []).pop() === n.slice(0, -2))
+ : e === t,
+ ),
+ a = (e, t, n) => {
+ if (0 === t.length) return !0;
+ var i = ((e) => {
+ var t = "";
+ if (T(e)) {
+ var n = r(e),
+ i = o(n);
+ i && (t = l(i));
+ } else t = e.type;
+ return t;
+ })(e);
+ return n
+ ? new Promise((T, E) => {
+ n(e, i)
+ .then((e) => {
+ u(t, e) ? T() : E();
+ })
+ .catch(E);
+ })
+ : u(t, i);
+ };
+ return (
+ t("SET_ATTRIBUTE_TO_OPTION_MAP", (e) =>
+ Object.assign(e, { accept: "acceptedFileTypes" }),
+ ),
+ t("ALLOW_HOPPER_ITEM", (e, t) => {
+ var n = t.query;
+ return (
+ !n("GET_ALLOW_FILE_TYPE_VALIDATION") ||
+ a(e, n("GET_ACCEPTED_FILE_TYPES"))
+ );
+ }),
+ t("LOAD_FILE", (e, t) => {
+ var n = t.query;
+ return new Promise((t, i) => {
+ if (n("GET_ALLOW_FILE_TYPE_VALIDATION")) {
+ var T = n("GET_ACCEPTED_FILE_TYPES"),
+ l = n("GET_FILE_VALIDATE_TYPE_DETECT_TYPE"),
+ o = a(e, T, l),
+ r = () => {
+ var e,
+ t = T.map(
+ ((e = n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES_MAP")),
+ (t) => null !== e[t] && (e[t] || t)),
+ ).filter((e) => !1 !== e),
+ l = t.filter((e, n) => t.indexOf(e) === n);
+ i({
+ status: {
+ main: n("GET_LABEL_FILE_TYPE_NOT_ALLOWED"),
+ sub: E(n("GET_FILE_VALIDATE_TYPE_LABEL_EXPECTED_TYPES"), {
+ allTypes: l.join(", "),
+ allButLastType: l.slice(0, -1).join(", "),
+ lastType: l[l.length - 1],
+ }),
+ },
+ });
+ };
+ if ("boolean" == typeof o) return o ? t(e) : r();
+ o.then(() => {
+ t(e);
+ }).catch(r);
+ } else t(e);
+ });
+ }),
+ {
+ options: {
+ allowFileTypeValidation: [!0, i.BOOLEAN],
+ acceptedFileTypes: [[], i.ARRAY],
+ labelFileTypeNotAllowed: ["File is of invalid type", i.STRING],
+ fileValidateTypeLabelExpectedTypes: [
+ "Expects {allButLastType} or {lastType}",
+ i.STRING,
+ ],
+ fileValidateTypeLabelExpectedTypesMap: [{}, i.OBJECT],
+ fileValidateTypeDetectType: [null, i.FUNCTION],
+ },
+ }
+ );
+ };
+ return (
+ "undefined" != typeof window &&
+ void 0 !== window.document &&
+ document.dispatchEvent(
+ new CustomEvent("FilePond:pluginloaded", { detail: e }),
+ ),
+ e
+ );
+});
diff --git a/app/public/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js b/app/public/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js
index 90cc07f..ccfe14e 100644
--- a/app/public/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js
+++ b/app/public/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js
@@ -6,4 +6,92 @@
/* eslint-disable */
-!function(A,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(A=A||self).FilePondPluginImageExifOrientation=e()}(this,function(){"use strict";var A=65496,e=65505,n=1165519206,t=18761,i=274,r=65280,o=function(A,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return A.getUint16(e,n)},a=function(A,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return A.getUint32(e,n)},u="undefined"!=typeof window&&void 0!==window.document,d=void 0,f=u?new Image:{};f.onload=function(){return d=f.naturalWidth>f.naturalHeight},f.src="data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QA6RXhpZgAATU0AKgAAAAgAAwESAAMAAAABAAYAAAEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAIBASIA/8QAJgABAAAAAAAAAAAAAAAAAAAAAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwBH/9k=";var l=function(u){var f=u.addFilter,l=u.utils,c=l.Type,g=l.isFile;return f("DID_LOAD_ITEM",function(u,f){var l=f.query;return new Promise(function(f,c){var s=u.file;if(!(g(s)&&function(A){return/^image\/jpeg/.test(A.type)}(s)&&l("GET_ALLOW_IMAGE_EXIF_ORIENTATION")&&d))return f(u);(function(u){return new Promise(function(d,f){var l=new FileReader;l.onload=function(u){var f=new DataView(u.target.result);if(o(f,0)===A){for(var l=f.byteLength,c=2;c {
+ "object" == typeof exports && "undefined" != typeof module
+ ? (module.exports = e())
+ : "function" == typeof define && define.amd
+ ? define(e)
+ : ((A = A || self).FilePondPluginImageExifOrientation = e());
+})(this, () => {
+ var A = 65496,
+ e = 65505,
+ n = 1165519206,
+ t = 18761,
+ i = 274,
+ r = 65280,
+ o = function (A, e) {
+ var n = arguments.length > 2 && void 0 !== arguments[2] && arguments[2];
+ return A.getUint16(e, n);
+ },
+ a = function (A, e) {
+ var n = arguments.length > 2 && void 0 !== arguments[2] && arguments[2];
+ return A.getUint32(e, n);
+ },
+ u = "undefined" != typeof window && void 0 !== window.document,
+ d = void 0,
+ f = u ? new Image() : {};
+ (f.onload = () => (d = f.naturalWidth > f.naturalHeight)),
+ (f.src =
+ "data:image/jpg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/4QA6RXhpZgAATU0AKgAAAAgAAwESAAMAAAABAAYAAAEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAIBASIA/8QAJgABAAAAAAAAAAAAAAAAAAAAAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwBH/9k=");
+ var l = (u) => {
+ var f = u.addFilter,
+ l = u.utils,
+ c = l.Type,
+ g = l.isFile;
+ return (
+ f("DID_LOAD_ITEM", (u, f) => {
+ var l = f.query;
+ return new Promise((f, c) => {
+ var s = u.file;
+ if (
+ !(
+ g(s) &&
+ ((A) => /^image\/jpeg/.test(A.type))(s) &&
+ l("GET_ALLOW_IMAGE_EXIF_ORIENTATION") &&
+ d
+ )
+ )
+ return f(u);
+ ((u) =>
+ new Promise((d, f) => {
+ var l = new FileReader();
+ (l.onload = (u) => {
+ var f = new DataView(u.target.result);
+ if (o(f, 0) === A) {
+ for (var l = f.byteLength, c = 2; c < l; ) {
+ var g = o(f, c);
+ if (((c += 2), g === e)) {
+ if (a(f, (c += 2)) !== n) break;
+ var s = o(f, (c += 6)) === t;
+ c += a(f, c + 4, s);
+ var v = o(f, c, s);
+ c += 2;
+ for (var w = 0; w < v; w++)
+ if (o(f, c + 12 * w, s) === i)
+ return void d(o(f, c + 12 * w + 8, s));
+ } else {
+ if ((g & r) !== r) break;
+ c += o(f, c);
+ }
+ }
+ d(-1);
+ } else d(-1);
+ }),
+ l.readAsArrayBuffer(u.slice(0, 65536));
+ }))(s).then((A) => {
+ u.setMetadata("exif", { orientation: A }), f(u);
+ });
+ });
+ }),
+ { options: { allowImageExifOrientation: [!0, c.BOOLEAN] } }
+ );
+ };
+ return (
+ "undefined" != typeof window &&
+ void 0 !== window.document &&
+ document.dispatchEvent(
+ new CustomEvent("FilePond:pluginloaded", { detail: l }),
+ ),
+ l
+ );
+});
diff --git a/app/public/partage/fragments/draft.php b/app/public/partage/fragments/draft.php
new file mode 100644
index 0000000..9de578f
--- /dev/null
+++ b/app/public/partage/fragments/draft.php
@@ -0,0 +1,98 @@
+ 'Token de sécurité invalide.']);
+ exit;
+ }
+}
+
+// ── Slug validation ─────────────────────────────────────────────────────
+$slug = $_GET['slug'] ?? ($_POST['slug'] ?? '');
+if (!preg_match('#^\d{8}-[A-Z0-9+/]{8}$#', $slug)) {
+ http_response_code(400);
+ header('Content-Type: application/json');
+ echo json_encode(['error' => 'Slug invalide.']);
+ exit;
+}
+
+// Draft storage key
+$draftKey = 'partage_draft_' . $slug;
+
+// ── POST: save all form fields ──────────────────────────────────────────
+if ($method === 'POST') {
+ // Fields that should never be persisted as drafts
+ $excludePrefixes = [
+ 'csrf_token', 'share_link_token', 'share_password',
+ 'filepond_mode', 'queue_file', 'filepond_',
+ ];
+ $excludeExact = ['slug', 'couverture', 'note_intention', 'files', 'annexes',
+ 'peertube_video', 'peertube_audio', 'cover_remove',
+ 'go', 'MAX_FILE_SIZE'];
+
+ $draft = [];
+ foreach ($_POST as $key => $value) {
+ // Skip excluded fields
+ if (in_array($key, $excludeExact, true)) continue;
+ $skip = false;
+ foreach ($excludePrefixes as $prefix) {
+ if (str_starts_with($key, $prefix)) { $skip = true; break; }
+ }
+ if ($skip) continue;
+
+ // Skip empty values (but keep '0' as valid)
+ if ($value === '' || $value === null || (is_array($value) && count($value) === 0)) {
+ continue;
+ }
+
+ $draft[$key] = $value;
+ }
+
+ $_SESSION[$draftKey] = $draft;
+
+ // Rotate CSRF after mutation — keep share CSRF in sync
+ $newToken = bin2hex(random_bytes(32));
+ $_SESSION['csrf_token'] = $newToken;
+ $_SESSION['share_csrf_' . $slug] = $newToken;
+
+ header('Content-Type: application/json');
+ echo json_encode([
+ 'success' => true,
+ 'csrf_token' => $newToken,
+ ]);
+ exit;
+}
+
+// ── GET: return draft fields for hydration ──────────────────────────────
+header('Content-Type: application/json');
+$draft = $_SESSION[$draftKey] ?? [];
+echo json_encode([
+ 'success' => true,
+ 'draft' => $draft,
+]);
+exit;
diff --git a/app/src/AppLogger.php b/app/src/AppLogger.php
index 73453fd..391d582 100644
--- a/app/src/AppLogger.php
+++ b/app/src/AppLogger.php
@@ -8,15 +8,8 @@
*/
class AppLogger
{
- private string $logDir;
- private string $logFile;
-
- public function __construct(?string $logDir = null)
+ public function __construct()
{
- $this->logDir = $logDir ?? (defined('STORAGE_ROOT') ? STORAGE_ROOT . '/logs' : __DIR__ . '/../storage/logs');
-
- // Keep for backward compat — actual file I/O is now handled by Monolog via Logger::get('app')
- $this->logFile = $this->logDir . '/form-submissions.log';
}
/**
diff --git a/app/src/Controllers/ExportController.php b/app/src/Controllers/ExportController.php
index 10a510c..1a115ab 100644
--- a/app/src/Controllers/ExportController.php
+++ b/app/src/Controllers/ExportController.php
@@ -175,7 +175,7 @@ class ExportController
$ptInstanceUrl = $this->getPeerTubeInstanceUrl();
}
$tid = (int) $f['thesis_id'];
- $peertubeLinks[$tid] = $peertubeLinks[$tid] ?? ['dirname' => '', 'links' => []];
+ $peertubeLinks[$tid] ??= ['dirname' => '', 'links' => []];
$peertubeLinks[$tid]['links'][] = [
'uuid' => $uuid,
'url' => $ptInstanceUrl !== '' ? rtrim($ptInstanceUrl, '/') . '/videos/watch/' . $uuid : '',
diff --git a/app/src/Controllers/TfeController.php b/app/src/Controllers/TfeController.php
index a71e688..e8117e7 100644
--- a/app/src/Controllers/TfeController.php
+++ b/app/src/Controllers/TfeController.php
@@ -107,10 +107,14 @@ class TfeController
. ' – XAMXAM';
// Editable messages
- $restrictedMessage = $this->db->getSetting('tfe_restricted_message',
- 'Les fichiers attachés à ce TFE sont réservés aux utilisateur·ices autorisé·es.');
- $forbiddenMessage = $this->db->getSetting('tfe_forbidden_message',
- "Ce TFE n'est pas disponible en ligne.");
+ $restrictedMessage = $this->db->getSetting(
+ 'tfe_restricted_message',
+ 'Les fichiers attachés à ce TFE sont réservés aux utilisateur·ices autorisé·es.'
+ );
+ $forbiddenMessage = $this->db->getSetting(
+ 'tfe_forbidden_message',
+ "Ce TFE n'est pas disponible en ligne."
+ );
return [
// Core data
diff --git a/app/src/Controllers/ThesisEditController.php b/app/src/Controllers/ThesisEditController.php
index b38121d..a304b8c 100644
--- a/app/src/Controllers/ThesisEditController.php
+++ b/app/src/Controllers/ThesisEditController.php
@@ -104,7 +104,7 @@ class ThesisEditController
$licenseTypes = $this->db->getAllLicenseTypes();
$enabledAccessTypes = $this->db->getEnabledFormAccessTypes();
- $rawRow = $this->db->getThesisRawFields($thesisId);
+ $rawRow = $this->db->getThesisRawFields($thesisId) ?? [];
$currentLicenseId = $rawRow['license_id'] ?? null;
$currentAccessTypeId = $rawRow['access_type_id'] ?? null;
$currentContextNote = $rawRow['context_note'] ?? '';
diff --git a/app/src/Database.php b/app/src/Database.php
index 62515e2..5c0c8ae 100644
--- a/app/src/Database.php
+++ b/app/src/Database.php
@@ -2016,7 +2016,7 @@ class Database
* Return the raw FK fields not exposed through v_theses_full string columns.
* Returns ['license_id', 'access_type_id', 'context_note'] or null if not found.
*
- * @return array{license_id:int|null,access_type_id:int|null,context_note:string}|null
+ * @return array{license_id:int|null,access_type_id:int|null,context_note:string,contact_visible:int|null}|null
*/
public function getThesisRawFields(int $thesisId): ?array
{
diff --git a/app/src/FilepondHandler.php b/app/src/FilepondHandler.php
index a185d04..4132f58 100644
--- a/app/src/FilepondHandler.php
+++ b/app/src/FilepondHandler.php
@@ -146,7 +146,7 @@ class FilepondHandler
// Track temp file in session so it survives page reloads
if (session_status() === PHP_SESSION_ACTIVE) {
- $_SESSION['filepond_tmp'][$queueType] = $_SESSION['filepond_tmp'][$queueType] ?? [];
+ $_SESSION['filepond_tmp'][$queueType] ??= [];
$_SESSION['filepond_tmp'][$queueType][] = $fileId;
}
diff --git a/app/src/Logger.php b/app/src/Logger.php
index b441389..e0287cd 100644
--- a/app/src/Logger.php
+++ b/app/src/Logger.php
@@ -1,8 +1,8 @@
tag
+ * bool $showAutosaveStatus — render the "Brouillon enregistré" status indicator
+ *
* Website:
* string $existingWebsiteUrl
* string $existingWebsiteLabel
@@ -56,6 +60,8 @@
// ── Defaults ──────────────────────────────────────────────────────────────────
$mode = $mode ?? 'add';
+$formExtraAttrs = $formExtraAttrs ?? '';
+$showAutosaveStatus = $showAutosaveStatus ?? false;
// In admin add/edit, no field is required (admins can save partial records)
$adminMode = ($mode === 'add' || $mode === 'edit');
$formAction = $formAction ?? '';
@@ -146,7 +152,7 @@ $errorFieldName = $errorFieldName ?? null;
-