mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
refactor: extract inline JS into app/ modules, remove dead overtype-webcomponent
- Remove overtype-webcomponent.min.js (zero references) - Extract copyLogContent + fallbackCopy + HTMX tab-updater → app/admin-logs.js (removes duplicate from both system.php and parametres.php) - Extract copyUrl → app/clipboard.js (shared by acces.php) - Extract tag/language pill-search logic → app/pill-search.js Generalized with data-pill-search attributes, auto-inits via DOMContentLoaded + htmx:afterSwap - Extract access-request form handler → app/access-request.js (was inline in templates/public/tfe.php) Files created: admin-logs.js, clipboard.js, pill-search.js, access-request.js Files modified: 9 templates/controllers to drop inline scripts and reference external JS files
This commit is contained in:
9
TODO.md
9
TODO.md
@@ -32,6 +32,15 @@
|
||||
- [x] Add `hx-target` response divs to the three fieldsets in contenus.php
|
||||
- [x] Update settings.php to return HTML toast on HTMX requests
|
||||
|
||||
## JS Refactoring — Extract inline scripts into app/ files
|
||||
|
||||
- [x] Remove overtype-webcomponent.min.js (unused)
|
||||
- [x] Extract copyLogContent + fallbackCopy + tab-updater → app/admin-logs.js
|
||||
- [x] Extract copyUrl → app/clipboard.js
|
||||
- [x] Extract tag-search inline script → app/pill-search.js (generalized for tag + language)
|
||||
- [x] Extract tfe.php access-request form → app/access-request.js
|
||||
- [x] Update all templates to use new external JS files
|
||||
|
||||
## Production Error Fixes (2026-05-11 remote logs)
|
||||
|
||||
- [x] **413 Request Entity Too Large** — bumped `client_max_body_size` to 256M, PHP post/upload to 256M, timeouts to 300s
|
||||
|
||||
@@ -29,6 +29,7 @@ extract($vars);
|
||||
$pageTitle = 'Accès';
|
||||
$isAdmin = true;
|
||||
$bodyClass = 'admin-body';
|
||||
$extraJs = ['/assets/js/app/clipboard.js'];
|
||||
|
||||
require_once APP_ROOT . '/templates/head.php';
|
||||
echo '<link rel="stylesheet" href="/assets/css/file-access.css">';
|
||||
|
||||
@@ -55,7 +55,7 @@ function wasSelected($key, $value) {
|
||||
$isAdmin = true;
|
||||
$bodyClass = 'admin-body';
|
||||
$extraCss = ['/assets/css/form.css', '/assets/css/filepond.min.css', '/assets/css/filepond-plugin-image-preview.min.css'];
|
||||
$extraJs = ['/assets/js/filepond.min.js', '/assets/js/filepond-plugin-file-validate-type.min.js', '/assets/js/filepond-plugin-file-validate-size.min.js', '/assets/js/filepond-plugin-image-preview.min.js', '/assets/js/filepond-plugin-image-exif-orientation.min.js', '/assets/js/file-upload-filepond.js', '/assets/js/beforeunload-guard.js', '/assets/js/upload-progress.js'];
|
||||
$extraJs = ['/assets/js/vendor/filepond.min.js', '/assets/js/vendor/filepond-plugin-file-validate-type.min.js', '/assets/js/vendor/filepond-plugin-file-validate-size.min.js', '/assets/js/vendor/filepond-plugin-image-preview.min.js', '/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js', '/assets/js/app/file-upload-filepond.js', '/assets/js/app/beforeunload-guard.js', '/assets/js/app/upload-progress.js', '/assets/js/app/pill-search.js'];
|
||||
require_once APP_ROOT . '/templates/head.php';
|
||||
include APP_ROOT . '/templates/header.php';
|
||||
include APP_ROOT . '/templates/admin/add.php';
|
||||
|
||||
@@ -70,7 +70,7 @@ $extraJsInline = '';
|
||||
|
||||
if ($editType === 'page' || $editType === 'about_page') {
|
||||
$initialContent = $page["content"] ?? "";
|
||||
$extraJs = ["/assets/js/overtype.min.js"];
|
||||
$extraJs = ["/assets/js/vendor/overtype.min.js"];
|
||||
$extraJsInline = <<<'JS'
|
||||
var OT = window.OverType.default || window.OverType;
|
||||
var hidden = document.getElementById('content');
|
||||
@@ -83,7 +83,7 @@ var editor = new OT(document.getElementById('editor'), {
|
||||
JS;
|
||||
} elseif ($editType === 'form_help') {
|
||||
$initialContent = $formHelpContent;
|
||||
$extraJs = ["/assets/js/overtype.min.js"];
|
||||
$extraJs = ["/assets/js/vendor/overtype.min.js"];
|
||||
$extraJsInline = <<<'JS'
|
||||
var OT = window.OverType.default || window.OverType;
|
||||
var hidden = document.getElementById('content');
|
||||
|
||||
@@ -40,7 +40,7 @@ try {
|
||||
|
||||
$isAdmin = true; $bodyClass = 'admin-body';
|
||||
$extraCss = ['/assets/css/form.css', '/assets/css/filepond.min.css', '/assets/css/filepond-plugin-image-preview.min.css'];
|
||||
$extraJs = ['/assets/js/filepond.min.js', '/assets/js/filepond-plugin-file-validate-type.min.js', '/assets/js/filepond-plugin-file-validate-size.min.js', '/assets/js/filepond-plugin-image-preview.min.js', '/assets/js/filepond-plugin-image-exif-orientation.min.js', '/assets/js/file-upload-filepond.js', '/assets/js/beforeunload-guard.js', '/assets/js/upload-progress.js'];
|
||||
$extraJs = ['/assets/js/vendor/filepond.min.js', '/assets/js/vendor/filepond-plugin-file-validate-type.min.js', '/assets/js/vendor/filepond-plugin-file-validate-size.min.js', '/assets/js/vendor/filepond-plugin-image-preview.min.js', '/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js', '/assets/js/app/file-upload-filepond.js', '/assets/js/app/beforeunload-guard.js', '/assets/js/app/upload-progress.js', '/assets/js/app/pill-search.js'];
|
||||
require_once APP_ROOT . '/templates/head.php';
|
||||
include APP_ROOT . '/templates/header.php';
|
||||
include APP_ROOT . '/templates/admin/edit.php';
|
||||
|
||||
97
app/public/assets/js/app/access-request.js
Normal file
97
app/public/assets/js/app/access-request.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* access-request.js — handles the "Demander l'accès" form on public thesis pages.
|
||||
*
|
||||
* Shows/hides the justification textarea based on email domain (@erg.school / @erg.be).
|
||||
* Submits via fetch() to /request-access and displays success/error messages.
|
||||
* Handles the special "recipient_rejected" status to let the user fix their email.
|
||||
*
|
||||
* Expects a form with:
|
||||
* #access-request-form — the form (needs data-thesis-id)
|
||||
* #access-email — email input
|
||||
* #justification-container — wrapper div for justification
|
||||
* #access-justification — justification textarea
|
||||
* #access-request-message — message display div
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var form = document.getElementById('access-request-form');
|
||||
if (!form) return;
|
||||
|
||||
var emailInput = document.getElementById('access-email');
|
||||
var justificationContainer = document.getElementById('justification-container');
|
||||
var justificationInput = document.getElementById('access-justification');
|
||||
var messageDiv = document.getElementById('access-request-message');
|
||||
|
||||
if (!emailInput || !messageDiv) return;
|
||||
|
||||
// Show/hide justification based on email domain
|
||||
emailInput.addEventListener('input', function () {
|
||||
var email = this.value.trim().toLowerCase();
|
||||
var isErg = email.endsWith('@erg.school') || email.endsWith('@erg.be');
|
||||
if (justificationContainer) justificationContainer.style.display = isErg ? 'none' : 'block';
|
||||
if (justificationInput) justificationInput.required = !isErg;
|
||||
});
|
||||
|
||||
function showRetryPrompt(rejectedEmail, serverMessage) {
|
||||
messageDiv.style.display = 'block';
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.innerHTML =
|
||||
'<strong>Adresse e-mail introuvable sur le serveur de l\'ERG.</strong><br>' +
|
||||
'<small>' + serverMessage.replace(/</g, '<') + '</small><br><br>' +
|
||||
'Corrigez votre adresse e-mail et réessayez.';
|
||||
emailInput.value = rejectedEmail;
|
||||
emailInput.classList.add('input-error');
|
||||
emailInput.focus();
|
||||
emailInput.select();
|
||||
emailInput.addEventListener('input', function clearError() {
|
||||
emailInput.classList.remove('input-error');
|
||||
emailInput.removeEventListener('input', clearError);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Envoi en cours...';
|
||||
messageDiv.style.display = 'none';
|
||||
|
||||
var submittedEmail = emailInput.value.trim();
|
||||
var formData = new FormData(form);
|
||||
formData.append('thesis_id', form.getAttribute('data-thesis-id'));
|
||||
|
||||
fetch('/request-access', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (data) {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Demander l\'accès';
|
||||
|
||||
if (data.status === 'recipient_rejected') {
|
||||
showRetryPrompt(submittedEmail, data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
messageDiv.style.display = 'block';
|
||||
if (data.success) {
|
||||
messageDiv.className = 'tfe-access-message tfe-access-success';
|
||||
messageDiv.textContent = data.message;
|
||||
form.reset();
|
||||
} else {
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.textContent = data.message || 'Une erreur est survenue. Veuillez réessayer.';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Demander l\'accès';
|
||||
messageDiv.style.display = 'block';
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.textContent = 'Erreur de connexion. Veuillez réessayer.';
|
||||
});
|
||||
});
|
||||
})();
|
||||
64
app/public/assets/js/app/admin-logs.js
Normal file
64
app/public/assets/js/app/admin-logs.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* admin-logs.js — log viewer utilities shared by system.php and parametres.php.
|
||||
*
|
||||
* Provides:
|
||||
* - copyLogContent(btn) — copy visible log lines to clipboard
|
||||
* - HTMX afterSwap handler to update active tab class on #sys-tab-panel
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.copyLogContent = function (btn) {
|
||||
var logOut = document.querySelector('#log-output');
|
||||
if (!logOut) return;
|
||||
var text = Array.from(logOut.querySelectorAll('.log-line'))
|
||||
.map(function (el) { return el.textContent; }).join('\n');
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
btn.textContent = '\u2713 Copi\u00e9';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function () { btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
});
|
||||
} else {
|
||||
window._fallbackCopy(text, btn);
|
||||
}
|
||||
};
|
||||
|
||||
window._fallbackCopy = function (text, btn) {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.cssText = 'position:fixed;opacity:0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
btn.textContent = '\u2713 Copi\u00e9';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function () { btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
} catch (e) {}
|
||||
document.body.removeChild(ta);
|
||||
};
|
||||
|
||||
// Update active tab class after each HTMX swap on #sys-tab-panel
|
||||
document.body.addEventListener('htmx:afterSwap', function (evt) {
|
||||
if (!(evt.detail.target && evt.detail.target.id === 'sys-tab-panel')) return;
|
||||
var rc = evt.detail.requestConfig;
|
||||
var tab = null;
|
||||
// Tab clicks carry ?tab=… in the path
|
||||
var qIdx = rc.path.indexOf('?');
|
||||
if (qIdx !== -1) {
|
||||
tab = new URLSearchParams(rc.path.substring(qIdx + 1)).get('tab');
|
||||
}
|
||||
// Line-count form sends tab via hx-vals in parameters
|
||||
if (!tab && rc.parameters && rc.parameters.tab) {
|
||||
tab = rc.parameters.tab;
|
||||
}
|
||||
if (!tab) return;
|
||||
document.querySelectorAll('.sys-tabs .sys-tab').forEach(function (a) {
|
||||
var isActive = a.getAttribute('data-tab') === tab;
|
||||
a.classList.toggle('active', isActive);
|
||||
if (isActive) a.setAttribute('aria-current', 'page');
|
||||
else a.removeAttribute('aria-current');
|
||||
});
|
||||
});
|
||||
})();
|
||||
38
app/public/assets/js/app/clipboard.js
Normal file
38
app/public/assets/js/app/clipboard.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* clipboard.js — lightweight URL copy helper.
|
||||
*
|
||||
* Usage:
|
||||
* <input type="hidden" id="url-123" value="https://...">
|
||||
* <button onclick="copyUrl(123)">Copier</button>
|
||||
*
|
||||
* Or with a custom selector pattern:
|
||||
* <button onclick="copyUrlFrom(document.getElementById('my-url'))">Copier</button>
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.copyUrl = function (id) {
|
||||
var input = document.getElementById('url-' + id);
|
||||
if (input) {
|
||||
window.copyUrlFrom(input);
|
||||
}
|
||||
};
|
||||
|
||||
window.copyUrlFrom = function (sourceEl) {
|
||||
var text = sourceEl.value || sourceEl.textContent || '';
|
||||
if (!text) return;
|
||||
navigator.clipboard.writeText(text).then(function () {
|
||||
var btn = window.event && window.event.target ? window.event.target.closest('button') : null;
|
||||
if (btn) {
|
||||
var origTitle = btn.getAttribute('title') || '';
|
||||
var origText = btn.textContent;
|
||||
btn.setAttribute('title', '\u2713 Copi\u00e9');
|
||||
btn.textContent = '\u2713 Copi\u00e9';
|
||||
setTimeout(function () {
|
||||
btn.setAttribute('title', origTitle);
|
||||
btn.textContent = origText;
|
||||
}, 1200);
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
171
app/public/assets/js/app/pill-search.js
Normal file
171
app/public/assets/js/app/pill-search.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* pill-search.js — generalized pill-based search component for tags and languages.
|
||||
*
|
||||
* Initialisez avec un conteneur ayant l'attribut data-pill-search :
|
||||
* <div data-pill-search data-pill-name="tag" data-pill-max="10" data-pill-min="3" data-pill-required="1">
|
||||
*
|
||||
* DOM attendu à l'intérieur du conteneur :
|
||||
* - .tag-search-pills → conteneur des pills
|
||||
* - .tag-search-input → champ de recherche (avec hx-post, hx-trigger, etc.)
|
||||
* - .tag-search-suggestions → dropdown
|
||||
* - .tag-search-count → compteur
|
||||
* - .tag-search-counter → wrapper du compteur
|
||||
* - .tag-search-input-wrap → wrapper du champ de recherche
|
||||
* - .tag-search-max-msg → message "maximum atteint"
|
||||
*
|
||||
* Options (par attribut data) :
|
||||
* data-pill-name → nom pour les inputs cachés (ex: "tag", "language_autre")
|
||||
* data-pill-max → max pills (default 10)
|
||||
* data-pill-min → min pills requis (default 0)
|
||||
* 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(function (container) {
|
||||
container.setAttribute('data-pill-search-initialized', '1');
|
||||
initPillSearch(container);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
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 (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)';
|
||||
}
|
||||
|
||||
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', 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 (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;
|
||||
|
||||
var escaped = htmlEscape(name);
|
||||
var pill = document.createElement('span');
|
||||
pill.className = 'tag-pill';
|
||||
pill.innerHTML = '<input type="hidden" name="' + inputName + '[]" value="' + escaped + '">'
|
||||
+ '<span class="tag-pill-name">' + escaped + '</span>'
|
||||
+ '<button type="button" class="tag-pill-remove" title="Retirer \u00AB\u00A0' + escaped + '\u00A0\u00BB" aria-label="Retirer ' + escaped + '">'
|
||||
+ '<svg width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>'
|
||||
+ '</button>';
|
||||
pills.appendChild(pill);
|
||||
updateCount();
|
||||
search.value = '';
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
search.focus();
|
||||
}
|
||||
|
||||
dropdown.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.tag-search-item');
|
||||
if (!btn) return;
|
||||
selectPill(btn);
|
||||
});
|
||||
|
||||
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', 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;
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginFileValidateSize 2.2.8
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* 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.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(function(e,i){return 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});
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginFileValidateType 1.2.9
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* 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});
|
||||
@@ -1,9 +0,0 @@
|
||||
/*!
|
||||
* FilePondPluginImageExifOrientation 1.0.11
|
||||
* Licensed under MIT, https://opensource.org/licenses/MIT/
|
||||
* Please visit https://pqina.nl/filepond/ for details.
|
||||
*/
|
||||
|
||||
/* 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<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(function(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});
|
||||
File diff suppressed because one or more lines are too long
9
app/public/assets/js/filepond.min.js
vendored
9
app/public/assets/js/filepond.min.js
vendored
File diff suppressed because one or more lines are too long
1
app/public/assets/js/htmx.min.js
vendored
1
app/public/assets/js/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
126
app/public/assets/js/overtype-webcomponent.min.js
vendored
126
app/public/assets/js/overtype-webcomponent.min.js
vendored
File diff suppressed because one or more lines are too long
1005
app/public/assets/js/overtype.min.js
vendored
1005
app/public/assets/js/overtype.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -396,15 +396,16 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/form.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/filepond.min.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/filepond-plugin-image-preview.min.css') ?>">
|
||||
<script src="<?= App::assetV('/assets/js/filepond.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-file-validate-type.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-file-validate-size.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-image-preview.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-image-exif-orientation.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/file-upload-filepond.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/beforeunload-guard.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/upload-progress.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/htmx.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/filepond.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/filepond-plugin-file-validate-type.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/filepond-plugin-file-validate-size.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/filepond-plugin-image-preview.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/filepond-plugin-image-exif-orientation.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/file-upload-filepond.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/beforeunload-guard.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/upload-progress.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/pill-search.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/htmx.min.js') ?>" defer></script>
|
||||
</head>
|
||||
<body class="student-body">
|
||||
<main id="main-content">
|
||||
|
||||
@@ -133,6 +133,7 @@ class TfeController
|
||||
// Layout
|
||||
'currentNav' => '',
|
||||
'extraCss' => ['/assets/css/tfe.css'],
|
||||
'extraJs' => ['/assets/js/app/access-request.js'],
|
||||
'bodyClass' => 'tfe-body',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1455,6 +1455,19 @@
|
||||
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||
+\\\\\\\ to: npqqxmut d463ff53 "fix: harden security based on pentest scan findings" (rebased revision)
|
||||
++ $linkName = $link['name'] ?? '';
|
||||
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: npqqxmut d463ff53 "fix: harden security based on pentest scan findings" (rebased revision)
|
||||
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||
- $linkName = $link['name'] ?? '';
|
||||
- $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff from: somsyvxz 14a3cd10 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebase destination)
|
||||
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ to: okvqnxxl ce3e339b "refactor: extract inline JS into app/ modules, remove dead overtype-webcomponent" (rebased revision)
|
||||
$linkName = $link['name'] ?? '';
|
||||
$linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||
$linkLockedYear = $link['locked_year'] ?? null;
|
||||
+%%%%%%% diff from: somsyvxz 249f7943 "Bulk bar anti-shift, tags icons, AP no-wrap, credits reorder" (rebased revision)
|
||||
+\\\\\\\ to: okvqnxxl 4c0538fc "refactor: extract inline JS into app/ modules, remove dead overtype-webcomponent" (rebased revision)
|
||||
++ $linkName = $link['name'] ?? '';
|
||||
++ $linkExpiresVal = $link['expires_at'] ? date('Y-m-d\TH:i', strtotime($link['expires_at'])) : '';
|
||||
?>
|
||||
<tr class="admin-table-row" onclick="event.stopPropagation(); window.open('/partage/<?= urlencode($link['slug']) ?>', '_blank')" style="cursor:pointer">
|
||||
@@ -1904,14 +1917,6 @@ document.getElementById('open-create-dialog').addEventListener('click', () => {
|
||||
document.getElementById('create-dialog').showModal();
|
||||
});
|
||||
|
||||
function copyUrl(id) {
|
||||
const input = document.getElementById('url-' + id);
|
||||
navigator.clipboard.writeText(input.value).then(() => {
|
||||
const btn = event.target.closest('button');
|
||||
if (btn) { const orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); setTimeout(() => btn.setAttribute('title', orig), 1200); }
|
||||
});
|
||||
}
|
||||
|
||||
function openEditDialog(id, name, hasPassword, expiresVal) {
|
||||
document.getElementById('edit-link-id').value = id;
|
||||
document.getElementById('edit-name').value = name || '';
|
||||
|
||||
@@ -390,7 +390,7 @@ document.addEventListener('htmx:afterSwap', function(evt) {
|
||||
<script>
|
||||
(function () {
|
||||
var otScript = document.createElement('script');
|
||||
otScript.src = '<?= App::assetV('/assets/js/overtype.min.js') ?>';
|
||||
otScript.src = '<?= App::assetV('/assets/js/vendor/overtype.min.js') ?>';
|
||||
document.head.appendChild(otScript);
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<?php if (!empty($extraJsInline)): ?>
|
||||
<script><?= $extraJsInline ?></script>
|
||||
<?php endif; ?>
|
||||
<script src="/assets/js/htmx.min.js"></script>
|
||||
<script src="/assets/js/vendor/htmx.min.js"></script>
|
||||
<script>
|
||||
// Global HTMX debugging for settings checkboxes
|
||||
document.body.addEventListener('htmx:sendError', function (e) {
|
||||
|
||||
@@ -454,31 +454,6 @@
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function copyLogContent(btn) {
|
||||
var logOut = document.querySelector('#log-output');
|
||||
if (!logOut) return;
|
||||
var text = Array.from(logOut.querySelectorAll('.log-line'))
|
||||
.map(function(el){ return el.textContent; }).join('\n');
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function(){
|
||||
btn.textContent = '\u2713 Copi\u00e9';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function(){ btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text, btn);
|
||||
}
|
||||
}
|
||||
function fallbackCopy(text, btn) {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.cssText = 'position:fixed;opacity:0';
|
||||
document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand('copy'); btn.textContent = '\u2713 Copi\u00e9'; btn.classList.add('copied');
|
||||
setTimeout(function(){ btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
} catch(e) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
// Focus the SMTP field that caused the probe error
|
||||
(function () {
|
||||
var form = document.querySelector('form[data-smtp-error-field]');
|
||||
@@ -489,30 +464,8 @@ function fallbackCopy(text, btn) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.focus();
|
||||
}());
|
||||
|
||||
// Update active tab class after each HTMX swap on #sys-tab-panel
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target && evt.detail.target.id === 'sys-tab-panel') {
|
||||
var rc = evt.detail.requestConfig;
|
||||
var tab = null;
|
||||
var qIdx = rc.path.indexOf('?');
|
||||
if (qIdx !== -1) {
|
||||
tab = new URLSearchParams(rc.path.substring(qIdx + 1)).get('tab');
|
||||
}
|
||||
if (!tab && rc.parameters && rc.parameters.tab) {
|
||||
tab = rc.parameters.tab;
|
||||
}
|
||||
if (tab) {
|
||||
document.querySelectorAll('.sys-tabs .sys-tab').forEach(function(a) {
|
||||
var isActive = a.getAttribute('data-tab') === tab;
|
||||
a.classList.toggle('active', isActive);
|
||||
if (isActive) a.setAttribute('aria-current', 'page');
|
||||
else a.removeAttribute('aria-current');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/assets/js/app/admin-logs.js"></script>
|
||||
|
||||
<!-- Enable maintenance confirm -->
|
||||
<dialog id="enable-maintenance-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="enable-maint-title">
|
||||
|
||||
@@ -111,55 +111,4 @@
|
||||
</div><!-- #sys-tab-panel -->
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function copyLogContent(btn) {
|
||||
var logOut = document.querySelector('#log-output');
|
||||
if (!logOut) return;
|
||||
var text = Array.from(logOut.querySelectorAll('.log-line'))
|
||||
.map(function(el){ return el.textContent; }).join('\n');
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(function(){
|
||||
btn.textContent = '\u2713 Copi\u00e9';
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function(){ btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text, btn);
|
||||
}
|
||||
}
|
||||
function fallbackCopy(text, btn) {
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.cssText = 'position:fixed;opacity:0';
|
||||
document.body.appendChild(ta); ta.select();
|
||||
try { document.execCommand('copy'); btn.textContent = '\u2713 Copi\u00e9'; btn.classList.add('copied');
|
||||
setTimeout(function(){ btn.textContent = 'Copier'; btn.classList.remove('copied'); }, 2000);
|
||||
} catch(e) {}
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
// Update active tab class after each HTMX swap on #sys-tab-panel
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target && evt.detail.target.id === 'sys-tab-panel') {
|
||||
var rc = evt.detail.requestConfig;
|
||||
var tab = null;
|
||||
// Tab clicks carry ?tab=… in the path
|
||||
var qIdx = rc.path.indexOf('?');
|
||||
if (qIdx !== -1) {
|
||||
tab = new URLSearchParams(rc.path.substring(qIdx + 1)).get('tab');
|
||||
}
|
||||
// Line-count form sends tab via hx-vals in parameters
|
||||
if (!tab && rc.parameters && rc.parameters.tab) {
|
||||
tab = rc.parameters.tab;
|
||||
}
|
||||
if (tab) {
|
||||
document.querySelectorAll('.sys-tabs .sys-tab').forEach(function(a) {
|
||||
var isActive = a.getAttribute('data-tab') === tab;
|
||||
a.classList.toggle('active', isActive);
|
||||
if (isActive) a.setAttribute('aria-current', 'page');
|
||||
else a.removeAttribute('aria-current');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/assets/js/app/admin-logs.js"></script>
|
||||
|
||||
@@ -31,7 +31,7 @@ $maxLanguages = $maxLanguages ?? 10;
|
||||
$required = $required ?? false;
|
||||
$langCount = count($selectedLanguages);
|
||||
?>
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container">
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container" data-pill-search data-pill-name="<?= htmlspecialchars($name) ?>" data-pill-max="<?= (int)$maxLanguages ?>" data-pill-min="0" data-pill-required="0" data-pill-role="lang">
|
||||
<span class="admin-row-label"><?= htmlspecialchars($label) ?><span id="language-autre-required"><?= $required ? ' <span class="asterisk">*</span>' : '' ?></span></span>
|
||||
<div class="tag-search-wrapper">
|
||||
<?php if ($hint): ?>
|
||||
@@ -79,183 +79,24 @@ $langCount = count($selectedLanguages);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline script for the interactive behaviour (no external JS required) -->
|
||||
<script>
|
||||
(function() {
|
||||
const container = document.getElementById(<?= json_encode($id . '-search-container') ?>);
|
||||
if (!container || container._langSearchInit) return;
|
||||
container._langSearchInit = true;
|
||||
|
||||
const pills = document.getElementById(<?= json_encode($id . '-pills') ?>);
|
||||
const search = document.getElementById(<?= json_encode($id . '-search') ?>);
|
||||
const dropdown = document.getElementById(<?= json_encode($id . '-suggestions') ?>);
|
||||
const countEl = document.getElementById(<?= json_encode($id . '-count') ?>);
|
||||
const counter = document.getElementById(<?= json_encode($id . '-counter') ?>);
|
||||
const maxLanguages = <?= (int)$maxLanguages ?>;
|
||||
const inputName = <?= json_encode($name) ?>;
|
||||
let selectedIdx = -1;
|
||||
|
||||
function updateCount() {
|
||||
const n = pills.querySelectorAll('.tag-pill').length;
|
||||
if (countEl) countEl.textContent = n + '/' + maxLanguages;
|
||||
if (counter) counter.style.display = (n > 0) ? '' : 'none';
|
||||
|
||||
// Toggle the checkbox-list asterisk: if any "autre" language pill
|
||||
// is present, the checkbox list is no longer required.
|
||||
const asteriskEl = document.getElementById('languages-required-asterisk');
|
||||
if (asteriskEl) {
|
||||
const checkboxes = document.querySelectorAll('#languages-fieldset input[type="checkbox"]:checked');
|
||||
asteriskEl.innerHTML = (n === 0 && checkboxes.length === 0) ? ' <span class="asterisk">*</span>' : '';
|
||||
}
|
||||
|
||||
// Show/hide search input based on max
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const maxMsg = container.querySelector('.tag-search-max-msg');
|
||||
if (n >= maxLanguages) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
// Lowercase, collapse spaces, trim, ucfirst for display
|
||||
function normalizeLang(name) {
|
||||
return name.trim().replace(/\s+/g, ' ').toLowerCase();
|
||||
}
|
||||
|
||||
function ucfirst(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
// Check if language already exists in pills (case-insensitive)
|
||||
function langAlreadyAdded(name) {
|
||||
const norm = normalizeLang(name);
|
||||
const existing = pills.querySelectorAll('.tag-pill-name');
|
||||
for (const el of existing) {
|
||||
if (normalizeLang(el.textContent) === norm) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a pill
|
||||
pills.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.tag-pill-remove');
|
||||
if (!btn) return;
|
||||
const pill = btn.closest('.tag-pill');
|
||||
pill.remove();
|
||||
updateCount();
|
||||
// Re-enable search field visibility
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const searchInput = container.querySelector('.tag-search-input');
|
||||
if (wrap && searchInput) {
|
||||
wrap.style.display = '';
|
||||
searchInput.style.display = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight a suggestion by index
|
||||
function highlight(idx) {
|
||||
const items = dropdown.querySelectorAll('.tag-search-item');
|
||||
items.forEach(function(item, i) {
|
||||
if (i === idx) {
|
||||
item.classList.add('tag-search-item--highlight');
|
||||
} else {
|
||||
item.classList.remove('tag-search-item--highlight');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Select a suggestion by button element
|
||||
function selectLang(btn) {
|
||||
const langName = normalizeLang(btn.getAttribute('data-tag-name') || '');
|
||||
if (!langName) return;
|
||||
|
||||
if (langAlreadyAdded(langName)) return;
|
||||
if (pills.querySelectorAll('.tag-pill').length >= maxLanguages) return;
|
||||
|
||||
const escapedName = htmlEscape(langName);
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'tag-pill';
|
||||
pill.innerHTML = '<input type="hidden" name="' + inputName + '[]" value="' + escapedName + '">'
|
||||
+ '<span class="tag-pill-name">' + escapedName + '</span>'
|
||||
+ '<button type="button" class="tag-pill-remove" title="Retirer \u00AB\u00A0' + escapedName + '\u00A0\u00BB" aria-label="Retirer ' + escapedName + '">'
|
||||
+ '<svg width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>'
|
||||
+ '</button>';
|
||||
pills.appendChild(pill);
|
||||
updateCount();
|
||||
search.value = '';
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
search.focus();
|
||||
}
|
||||
|
||||
// Click on suggestion
|
||||
dropdown.addEventListener('click', function(e) {
|
||||
console.log('[lang-search] dropdown click, target:', e.target.tagName, e.target.className);
|
||||
const btn = e.target.closest('.tag-search-item');
|
||||
if (!btn) { console.log('[lang-search] no .tag-search-item found in click path'); return; }
|
||||
console.log('[lang-search] found btn:', btn.getAttribute('data-tag-name'), btn.className);
|
||||
selectLang(btn);
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
search.addEventListener('keydown', function(e) {
|
||||
const 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) {
|
||||
selectLang(items[selectedIdx]);
|
||||
} else {
|
||||
selectLang(items[0]);
|
||||
}
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
});
|
||||
|
||||
// Hide dropdown on blur (after a tiny delay so click events fire)
|
||||
search.addEventListener('blur', function() {
|
||||
setTimeout(function() {
|
||||
if (!dropdown.contains(document.activeElement)) {
|
||||
console.log('[lang-search] blur: hiding dropdown');
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Log HTMX responses
|
||||
document.body.addEventListener('htmx:afterSwap', function(e) {
|
||||
if (e.detail.target && e.detail.target.id === '<?= htmlspecialchars($id) ?>-suggestions') {
|
||||
console.log('[lang-search] htmx:afterSwap, target:', e.detail.target.id, 'html length:', e.detail.target.innerHTML.length);
|
||||
console.log('[lang-search] innerHTML:', e.detail.target.innerHTML);
|
||||
}
|
||||
});
|
||||
|
||||
function htmlEscape(str) {
|
||||
const el = document.createElement('span');
|
||||
el.textContent = str;
|
||||
return el.innerHTML;
|
||||
// Language-specific: toggle checkbox-list asterisk based on pills presence
|
||||
(function () {
|
||||
var container = document.getElementById(<?= json_encode($id . '-search-container') ?>);
|
||||
if (!container) return;
|
||||
var pills = container.querySelector('.tag-search-pills');
|
||||
if (!pills) return;
|
||||
function check() {
|
||||
var asteriskEl = document.getElementById('languages-required-asterisk');
|
||||
if (!asteriskEl) return;
|
||||
var n = pills.querySelectorAll('.tag-pill').length;
|
||||
var checkboxes = document.querySelectorAll('#languages-fieldset input[type="checkbox"]:checked');
|
||||
asteriskEl.innerHTML = (n === 0 && checkboxes.length === 0) ? ' <span class="asterisk">*</span>' : '';
|
||||
}
|
||||
var observer = new MutationObserver(check);
|
||||
observer.observe(pills, { childList: true });
|
||||
check();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
unset($name, $label, $placeholder, $hint, $hxPost, $selectedLanguages, $id, $maxLanguages, $langCount, $required);
|
||||
|
||||
@@ -34,7 +34,7 @@ $required = $required ?? false;
|
||||
$tagCount = count($selectedTags);
|
||||
$belowMin = $required && $tagCount < $minTags;
|
||||
?>
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container">
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container" data-pill-search data-pill-name="<?= htmlspecialchars($name) ?>" data-pill-max="<?= (int)$maxTags ?>" data-pill-min="<?= (int)$minTags ?>" data-pill-required="<?= $required ? '1' : '0' ?>" data-pill-role="tag">
|
||||
<span class="admin-row-label"><?= htmlspecialchars($label) ?><?= $required ? ' <span class="asterisk">*</span>' : '' ?></span>
|
||||
<div class="tag-search-wrapper">
|
||||
<?php if ($hint): ?>
|
||||
@@ -81,171 +81,5 @@ $belowMin = $required && $tagCount < $minTags;
|
||||
<div class="tag-search-suggestions" id="<?= htmlspecialchars($id) ?>-suggestions" role="listbox"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline script for the interactive behaviour (no external JS required) -->
|
||||
<script>
|
||||
(function() {
|
||||
const container = document.getElementById(<?= json_encode($id . '-search-container') ?>);
|
||||
if (!container || container._tagSearchInit) return;
|
||||
container._tagSearchInit = true;
|
||||
|
||||
const pills = document.getElementById(<?= json_encode($id . '-pills') ?>);
|
||||
const search = document.getElementById(<?= json_encode($id . '-search') ?>);
|
||||
const dropdown = document.getElementById(<?= json_encode($id . '-suggestions') ?>);
|
||||
const countEl = document.getElementById(<?= json_encode($id . '-count') ?>);
|
||||
const counter = document.getElementById(<?= json_encode($id . '-counter') ?>);
|
||||
const maxTags = <?= (int)$maxTags ?>;
|
||||
const minTags = <?= (int)$minTags ?>;
|
||||
const required = <?= json_encode($required) ?>;
|
||||
const inputName = <?= json_encode($name) ?>;
|
||||
let selectedIdx = -1;
|
||||
|
||||
function updateCount() {
|
||||
const n = pills.querySelectorAll('.tag-pill').length;
|
||||
const suffix = required ? ' (min ' + minTags + ')' : '';
|
||||
if (countEl) countEl.textContent = n + '/' + maxTags + suffix;
|
||||
if (counter) counter.style.display = (n > 0 || required) ? '' : 'none';
|
||||
if (countEl && required) {
|
||||
if (n < minTags) {
|
||||
countEl.style.color = 'var(--text-danger)';
|
||||
} else {
|
||||
countEl.style.color = 'var(--accent)';
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide search input based on max
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const 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';
|
||||
}
|
||||
}
|
||||
|
||||
// Lowercase, collapse spaces, trim
|
||||
function normalizeTag(name) {
|
||||
return name.trim().replace(/\s+/g, ' ').toLowerCase();
|
||||
}
|
||||
|
||||
// Check if tag already exists in pills (case-insensitive)
|
||||
function tagAlreadyAdded(name) {
|
||||
const norm = normalizeTag(name);
|
||||
const existing = pills.querySelectorAll('.tag-pill-name');
|
||||
for (const el of existing) {
|
||||
if (normalizeTag(el.textContent) === norm) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a pill
|
||||
pills.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.tag-pill-remove');
|
||||
if (!btn) return;
|
||||
const pill = btn.closest('.tag-pill');
|
||||
pill.remove();
|
||||
updateCount();
|
||||
// Re-enable search field visibility
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const searchInput = container.querySelector('.tag-search-input');
|
||||
if (wrap && searchInput) {
|
||||
wrap.style.display = '';
|
||||
searchInput.style.display = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight a suggestion by index
|
||||
function highlight(idx) {
|
||||
const items = dropdown.querySelectorAll('.tag-search-item');
|
||||
items.forEach(function(item, i) {
|
||||
if (i === idx) {
|
||||
item.classList.add('tag-search-item--highlight');
|
||||
} else {
|
||||
item.classList.remove('tag-search-item--highlight');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Select a suggestion by button element
|
||||
function selectTag(btn) {
|
||||
const tagName = normalizeTag(btn.getAttribute('data-tag-name') || '');
|
||||
if (!tagName) return;
|
||||
|
||||
if (tagAlreadyAdded(tagName)) return;
|
||||
if (pills.querySelectorAll('.tag-pill').length >= maxTags) return;
|
||||
|
||||
const escapedName = htmlEscape(tagName);
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'tag-pill';
|
||||
pill.innerHTML = '<input type="hidden" name="' + inputName + '[]" value="' + escapedName + '">'
|
||||
+ '<span class="tag-pill-name">' + escapedName + '</span>'
|
||||
+ '<button type="button" class="tag-pill-remove" title="Retirer \u00AB\u00A0' + escapedName + '\u00A0\u00BB" aria-label="Retirer ' + escapedName + '">'
|
||||
+ '<svg width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>'
|
||||
+ '</button>';
|
||||
pills.appendChild(pill);
|
||||
updateCount();
|
||||
search.value = '';
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
search.focus();
|
||||
}
|
||||
|
||||
// Click on suggestion
|
||||
dropdown.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.tag-search-item');
|
||||
if (!btn) return;
|
||||
selectTag(btn);
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
search.addEventListener('keydown', function(e) {
|
||||
const 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) {
|
||||
selectTag(items[selectedIdx]);
|
||||
} else {
|
||||
selectTag(items[0]);
|
||||
}
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
});
|
||||
|
||||
// Hide dropdown on blur (after a tiny delay so click events fire)
|
||||
search.addEventListener('blur', function() {
|
||||
setTimeout(function() {
|
||||
if (!dropdown.contains(document.activeElement)) {
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
|
||||
function htmlEscape(str) {
|
||||
const el = document.createElement('span');
|
||||
el.textContent = str;
|
||||
return el.innerHTML;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
unset($name, $label, $placeholder, $hint, $hxPost, $selectedTags, $id, $maxTags, $tagCount);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- Student popover -->
|
||||
<div id="student-popover" class="student-popover" hidden aria-live="polite"></div>
|
||||
|
||||
<script src="/assets/js/htmx.min.js"></script>
|
||||
<script src="/assets/js/vendor/htmx.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var popover = document.getElementById('student-popover');
|
||||
|
||||
@@ -336,88 +336,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const form = document.getElementById('access-request-form');
|
||||
const emailInput = document.getElementById('access-email');
|
||||
const justificationContainer = document.getElementById('justification-container');
|
||||
const justificationInput = document.getElementById('access-justification');
|
||||
const messageDiv = document.getElementById('access-request-message');
|
||||
|
||||
// Show/hide justification based on email domain
|
||||
emailInput.addEventListener('input', function() {
|
||||
const email = this.value.trim().toLowerCase();
|
||||
const isErg = email.endsWith('@erg.school') || email.endsWith('@erg.be');
|
||||
justificationContainer.style.display = isErg ? 'none' : 'block';
|
||||
justificationInput.required = !isErg;
|
||||
});
|
||||
|
||||
function showRetryPrompt(rejectedEmail, serverMessage) {
|
||||
messageDiv.style.display = 'block';
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.innerHTML =
|
||||
'<strong>Adresse e-mail introuvable sur le serveur de l\'ERG.</strong><br>' +
|
||||
'<small>' + serverMessage.replace(/</g, '<') + '</small><br><br>' +
|
||||
'Corrigez votre adresse e-mail et réessayez.';
|
||||
// Highlight the email field and let the user fix it
|
||||
emailInput.value = rejectedEmail;
|
||||
emailInput.classList.add('input-error');
|
||||
emailInput.focus();
|
||||
emailInput.select();
|
||||
// Remove error highlight once they start typing
|
||||
emailInput.addEventListener('input', function clearError() {
|
||||
emailInput.classList.remove('input-error');
|
||||
emailInput.removeEventListener('input', clearError);
|
||||
});
|
||||
}
|
||||
|
||||
// Form submission
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Envoi en cours...';
|
||||
messageDiv.style.display = 'none';
|
||||
|
||||
const submittedEmail = emailInput.value.trim();
|
||||
const formData = new FormData(form);
|
||||
formData.append('thesis_id', '<?= $thesisId ?>');
|
||||
|
||||
fetch('/request-access', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Demander l\'accès';
|
||||
|
||||
if (data.status === 'recipient_rejected') {
|
||||
showRetryPrompt(submittedEmail, data.message);
|
||||
return;
|
||||
}
|
||||
|
||||
messageDiv.style.display = 'block';
|
||||
if (data.success) {
|
||||
messageDiv.className = 'tfe-access-message tfe-access-success';
|
||||
messageDiv.textContent = data.message;
|
||||
form.reset();
|
||||
} else {
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.textContent = data.message || 'Une erreur est survenue. Veuillez réessayer.';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Demander l\'accès';
|
||||
messageDiv.style.display = 'block';
|
||||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||||
messageDiv.textContent = 'Erreur de connexion. Veuillez réessayer.';
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php elseif (!empty($data["files"])): ?>
|
||||
<?php
|
||||
// Preload PeerTube instance URL once
|
||||
|
||||
Reference in New Issue
Block a user