Initial commit
This commit is contained in:
883
js/adminDashboardMods.js
Normal file
883
js/adminDashboardMods.js
Normal file
@@ -0,0 +1,883 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
function isPostEditPage() {
|
||||
return window.pagenow === 'post'
|
||||
|| window.pagenow === 'post-new'
|
||||
// On CPTs, pagenow is the post_type slug — also catch them via the
|
||||
// body classes WP sets for any post.php / post-new.php screen.
|
||||
|| document.body.classList.contains('post-php')
|
||||
|| document.body.classList.contains('post-new-php');
|
||||
}
|
||||
|
||||
function isProfileEditPage() {
|
||||
return window.pagenow === 'profile' || window.pagenow === 'user-edit' || window.pagenow === 'user-new';
|
||||
}
|
||||
|
||||
function getProfileForm() {
|
||||
return document.querySelector('#your-profile, #createuser');
|
||||
}
|
||||
|
||||
function isPodsModal() {
|
||||
return new URLSearchParams(window.location.search).has('pods_modal');
|
||||
}
|
||||
|
||||
function renameArticlesToAnnonces() {
|
||||
const replacements = [
|
||||
[/Tous les articles/g, 'Toutes les annonces'],
|
||||
[/Ajouter un article/g, 'Ajouter une annonce'],
|
||||
[/Modifier l.article/g, "Modifier l'annonce"],
|
||||
[/Pr\u00e9visualiser l.article/g, "Pr\u00e9visualiser l'annonce"],
|
||||
[/Afficher l.article/g, "Afficher l'annonce"],
|
||||
[/Voir l.article/g, "Voir l'annonce"],
|
||||
[/Article publi\u00e9/g, 'Annonce publi\u00e9e'],
|
||||
[/Article mis \u00e0 jour/g, 'Annonce mise \u00e0 jour'],
|
||||
[/Article planifi\u00e9/g, 'Annonce planifi\u00e9e'],
|
||||
[/Articles par page/g, 'Annonces par page'],
|
||||
[/Articles/g, 'Annonces'],
|
||||
[/Article/g, 'Annonce'],
|
||||
[/Rechercher des articles/g, 'Rechercher des annonces'],
|
||||
];
|
||||
|
||||
function applyReplacements(text) {
|
||||
return replacements.reduce((t, [s, r]) => t.replace(s, r), text);
|
||||
}
|
||||
|
||||
function replaceInTextNodes(el) {
|
||||
if (!el) return;
|
||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
||||
const nodes = [];
|
||||
while (walker.nextNode()) nodes.push(walker.currentNode);
|
||||
nodes.forEach(function(node) {
|
||||
const replaced = applyReplacements(node.textContent);
|
||||
if (replaced !== node.textContent) node.textContent = replaced;
|
||||
});
|
||||
}
|
||||
|
||||
// Menu latéral
|
||||
replaceInTextNodes(document.querySelector('#menu-posts'));
|
||||
|
||||
// Titre de page (h1) et bouton d'ajout
|
||||
document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes);
|
||||
|
||||
// Notifications après sauvegarde (Article publié, mis à jour…)
|
||||
document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes);
|
||||
|
||||
// Boîte de publication — lien "Voir l'article"
|
||||
replaceInTextNodes(document.querySelector('.submitbox'));
|
||||
|
||||
// Options d'écran — "Articles par page"
|
||||
replaceInTextNodes(document.querySelector('#screen-options-wrap'));
|
||||
|
||||
// Bouton de recherche (attribut value + aria-label)
|
||||
var searchSubmit = document.querySelector('#search-submit');
|
||||
if (searchSubmit) {
|
||||
if (searchSubmit.value) {
|
||||
searchSubmit.value = applyReplacements(searchSubmit.value);
|
||||
}
|
||||
var ariaLabel = searchSubmit.getAttribute('aria-label');
|
||||
if (ariaLabel) {
|
||||
searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel));
|
||||
}
|
||||
}
|
||||
|
||||
// Titre de l'onglet du navigateur
|
||||
document.title = applyReplacements(document.title);
|
||||
}
|
||||
|
||||
function updatePostboxVisibility() {
|
||||
document.querySelectorAll('.postbox').forEach((postBox) => {
|
||||
if (postBox.id.startsWith('pods')) {
|
||||
// body-en is controlled by language tabs — never auto-hide it
|
||||
if (postBox.id === 'pods-meta-body-en') return;
|
||||
const fields = postBox.querySelectorAll('tr');
|
||||
const hasVisibleFields = Array.from(fields).some(field => field.style.display !== 'none');
|
||||
postBox.style.display = hasVisibleFields ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Force Visual (TinyMCE) mode on page load.
|
||||
// WP stores the last-used editor mode in localStorage and restores it at document.ready.
|
||||
// When Code mode is restored, TinyMCE is never initialised — tinymce.get() returns null.
|
||||
// Instead, check the wrapper's CSS class:
|
||||
// tmce-active = Visual mode (fine)
|
||||
// html-active = Code mode (switch to Visual)
|
||||
function ensureVisualMode(editorId, attempt) {
|
||||
attempt = attempt || 0;
|
||||
if (attempt > 15) return;
|
||||
var wrap = document.getElementById('wp-' + editorId + '-wrap');
|
||||
if (!wrap) {
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
return;
|
||||
}
|
||||
if (wrap.classList.contains('html-active')) {
|
||||
var ed = window.tinymce && tinymce.get(editorId);
|
||||
if (!ed || !ed.initialized) {
|
||||
// TinyMCE not ready yet — retry rather than calling switchEditors.go() prematurely
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
return;
|
||||
}
|
||||
if (typeof switchEditors !== 'undefined') {
|
||||
switchEditors.go(editorId, 'tmce');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!wrap.classList.contains('tmce-active')) {
|
||||
// Mode not yet determined — retry
|
||||
setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: insert the tab bar and relocate #pods-meta-body-en.
|
||||
// The DOM move breaks TinyMCE's iframe (browsers reset iframe content on detach),
|
||||
// so we leave the container visible here and let Pods/TinyMCE initialise normally.
|
||||
// The broken iframe is repaired by reinitEditor() on first EN tab open.
|
||||
function setupBodyTabsDom() {
|
||||
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
|
||||
var bodyEnBox = document.getElementById('pods-meta-body-en');
|
||||
if (!nativeEditor || !bodyEnBox) return;
|
||||
|
||||
var tabBar = document.createElement('div');
|
||||
tabBar.className = 'body-lang-tabs';
|
||||
tabBar.innerHTML =
|
||||
'<button type="button" class="body-lang-tab is-active" data-panel="fr">Fran\u00e7ais</button>' +
|
||||
'<button type="button" class="body-lang-tab" data-panel="en">English</button>';
|
||||
nativeEditor.parentNode.insertBefore(tabBar, nativeEditor);
|
||||
|
||||
// Move EN metabox to sit right after the native editor for correct visual layout.
|
||||
// Do NOT hide it yet — Pods must init TinyMCE with the container visible so the
|
||||
// iframe can measure its dimensions. Page is still opacity:0 so no flash.
|
||||
nativeEditor.parentNode.insertBefore(bodyEnBox, nativeEditor.nextSibling);
|
||||
}
|
||||
|
||||
// Rebuild a TinyMCE editor whose iframe is broken (empty/non-interactive).
|
||||
// This happens when TinyMCE is initialised on a hidden (display:none) element:
|
||||
// the iframe can't measure dimensions and its document body stays empty.
|
||||
//
|
||||
// We reinit from tinyMCEPreInit.mceInit — first trying the editor's own config
|
||||
// (registered by Pods server-side), falling back to 'content' (the native WP editor).
|
||||
//
|
||||
// Inline toolbar positioning fix:
|
||||
// TinyMCE's 'wordpress' plugin captures document.getElementById(id+'_ifr') during
|
||||
// 'preinit' — before the iframe is created — so mceIframe is always null.
|
||||
// Fix: intercept getElementById during preinit so the 'wordpress' plugin captures
|
||||
// a proxyIframe instead of null. After init, proxy delegates to the real iframe.
|
||||
function reinitEditor(editorId) {
|
||||
var ed = window.tinymce && tinymce.get(editorId);
|
||||
// Preserve existing content before destroying the instance
|
||||
var savedContent = '';
|
||||
if (ed) {
|
||||
try { savedContent = ed.getContent(); } catch (e) {}
|
||||
ed.remove();
|
||||
}
|
||||
if (!savedContent) {
|
||||
var ta = document.getElementById(editorId);
|
||||
if (ta) savedContent = ta.value || '';
|
||||
}
|
||||
|
||||
if (!window.tinyMCEPreInit || !window.tinymce) return;
|
||||
|
||||
// Use the editor's own server-side config if available, else clone from 'content'
|
||||
var baseInit = (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[editorId])
|
||||
|| (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit['content']);
|
||||
if (!baseInit) return;
|
||||
|
||||
// Proxy iframe: getBoundingClientRect() falls back to the editor wrap
|
||||
var wrapId = 'wp-' + editorId + '-wrap';
|
||||
var proxyIframe = {
|
||||
getBoundingClientRect: function() {
|
||||
var el = document.getElementById(wrapId);
|
||||
return el ? el.getBoundingClientRect()
|
||||
: { top: 0, left: 0, right: window.innerWidth,
|
||||
bottom: window.innerHeight, width: window.innerWidth,
|
||||
height: window.innerHeight };
|
||||
}
|
||||
};
|
||||
|
||||
var savedGetById = document.getElementById;
|
||||
var origSetup = baseInit.setup;
|
||||
var content = savedContent;
|
||||
|
||||
tinymce.init($.extend({}, baseInit, {
|
||||
selector: '#' + editorId,
|
||||
setup: function(editor) {
|
||||
if (typeof origSetup === 'function') origSetup(editor);
|
||||
|
||||
editor.on('focus', function() {
|
||||
window.wpActiveEditor = editorId;
|
||||
});
|
||||
|
||||
editor.on('preinit', function() {
|
||||
document.getElementById = function(id) {
|
||||
if (id === editorId + '_ifr') return proxyIframe;
|
||||
return savedGetById.call(document, id);
|
||||
};
|
||||
setTimeout(function() {
|
||||
document.getElementById = savedGetById;
|
||||
}, 0);
|
||||
});
|
||||
|
||||
editor.on('init', function() {
|
||||
// Point proxy to real iframe
|
||||
var realIframe = savedGetById.call(document, editorId + '_ifr');
|
||||
if (realIframe) {
|
||||
proxyIframe.getBoundingClientRect = function() {
|
||||
return realIframe.getBoundingClientRect();
|
||||
};
|
||||
}
|
||||
// Restore content that was in the textarea
|
||||
if (content) {
|
||||
editor.setContent(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Phase 2: wire tab click handlers — runs at t=100ms after metabox reordering.
|
||||
function initBodyLanguageTabs() {
|
||||
var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
|
||||
var bodyEnBox = document.getElementById('pods-meta-body-en');
|
||||
var tabBar = document.querySelector('.body-lang-tabs');
|
||||
if (!nativeEditor || !bodyEnBox || !tabBar) {
|
||||
// body_en not available (e.g. contributor role) — still force visual mode on main editor
|
||||
if (nativeEditor) ensureVisualMode('content');
|
||||
return;
|
||||
}
|
||||
|
||||
var enEditorId = 'pods-form-ui-pods-meta-body-en';
|
||||
var enTmceReady = false;
|
||||
|
||||
// Hide EN panel — page is still opacity:0, user won't see the switch
|
||||
bodyEnBox.style.display = 'none';
|
||||
|
||||
tabBar.querySelectorAll('.body-lang-tab').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
tabBar.querySelectorAll('.body-lang-tab').forEach(function(b) {
|
||||
b.classList.remove('is-active');
|
||||
});
|
||||
btn.classList.add('is-active');
|
||||
|
||||
var revealedPanel;
|
||||
|
||||
if (btn.dataset.panel === 'fr') {
|
||||
bodyEnBox.style.display = 'none';
|
||||
nativeEditor.style.opacity = '0';
|
||||
nativeEditor.style.display = '';
|
||||
revealedPanel = nativeEditor;
|
||||
} else {
|
||||
nativeEditor.style.display = 'none';
|
||||
bodyEnBox.style.opacity = '0';
|
||||
bodyEnBox.style.display = 'block';
|
||||
revealedPanel = bodyEnBox;
|
||||
|
||||
if (!enTmceReady) {
|
||||
enTmceReady = true;
|
||||
// Reinit while container is visible so TinyMCE can measure dimensions
|
||||
reinitEditor(enEditorId);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify TinyMCE to reflow, then fade in once layout is correct
|
||||
setTimeout(function() {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
requestAnimationFrame(function() {
|
||||
requestAnimationFrame(function() {
|
||||
revealedPanel.style.opacity = '';
|
||||
});
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure both editors start in Visual (not Code) mode
|
||||
ensureVisualMode('content');
|
||||
ensureVisualMode(enEditorId);
|
||||
}
|
||||
|
||||
function groupAxesCheckboxes() {
|
||||
if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
|
||||
|
||||
var row = document.querySelector('.pods-form-ui-row-name-axes-thematiques');
|
||||
if (!row) return;
|
||||
|
||||
var list = row.querySelector('ul');
|
||||
if (!list) return;
|
||||
|
||||
// Already grouped — nothing to do
|
||||
if (list.querySelector('.axes-group-label')) return;
|
||||
|
||||
// Map existing <li> by checkbox value; preserve "add new" button
|
||||
var liMap = {};
|
||||
var addNewItem = null;
|
||||
list.querySelectorAll('li').forEach(function(li) {
|
||||
if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; }
|
||||
var cb = li.querySelector('input[type="checkbox"]');
|
||||
if (cb) liMap[cb.value] = li;
|
||||
});
|
||||
|
||||
// Rebuild list in group order
|
||||
list.innerHTML = '';
|
||||
thalimAxesGroups.forEach(function(group) {
|
||||
var labelLi = document.createElement('li');
|
||||
labelLi.className = 'axes-group-label';
|
||||
labelLi.textContent = group.label;
|
||||
list.appendChild(labelLi);
|
||||
group.terms.forEach(function(term) {
|
||||
var li = liMap[String(term.id)];
|
||||
if (li) list.appendChild(li);
|
||||
});
|
||||
});
|
||||
|
||||
if (addNewItem) list.appendChild(addNewItem);
|
||||
}
|
||||
|
||||
var REF_BIB_EDITOR_ID = 'pods-form-ui-pods-meta-reference-bibliographique';
|
||||
var refBibReinited = false;
|
||||
|
||||
// Reinit the référence bibliographique TinyMCE editor.
|
||||
// Called at page load (if the field is already visible) and by the
|
||||
// MutationObserver (when the field becomes visible after a category change).
|
||||
function initRefBibEditor() {
|
||||
if (refBibReinited) return;
|
||||
var row = document.querySelector('.pods-form-ui-row-name-reference-bibliographique');
|
||||
if (!row || row.style.display === 'none') return;
|
||||
refBibReinited = true;
|
||||
reinitEditor(REF_BIB_EDITOR_ID);
|
||||
ensureVisualMode(REF_BIB_EDITOR_ID);
|
||||
}
|
||||
|
||||
function initAxesGroupObserver() {
|
||||
// Pods shows/hides conditional rows by removing inline style="display:none"
|
||||
// Watch the entire Pods meta form for style changes on the axes row
|
||||
var podsForm = document.querySelector('.pods-pick-values, #pods-meta-champs-contextuels, form#post');
|
||||
if (!podsForm) podsForm = document.body;
|
||||
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var target = mutations[i].target;
|
||||
if (target.classList && target.classList.contains('pods-form-ui-row-name-axes-thematiques')) {
|
||||
if (target.style.display !== 'none') {
|
||||
setTimeout(groupAxesCheckboxes, 50);
|
||||
}
|
||||
}
|
||||
// Reinit TinyMCE on the référence bibliographique field when its
|
||||
// row becomes visible — Pods hides it with display:none which breaks
|
||||
// the TinyMCE iframe. Only reinit once per page load.
|
||||
if (!refBibReinited && target.classList &&
|
||||
target.classList.contains('pods-form-ui-row-name-reference-bibliographique')) {
|
||||
if (target.style.display !== 'none') {
|
||||
setTimeout(initRefBibEditor, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
|
||||
}
|
||||
|
||||
function initPostEditPage() {
|
||||
// Disable category options (CSS handles the color)
|
||||
const categorieSelect = document.querySelector('#pods-form-ui-pods-meta-categorie');
|
||||
if (categorieSelect) {
|
||||
const categoriesToDisable = ['1', '12', '5', '20'];
|
||||
categorieSelect.querySelectorAll('option').forEach(option => {
|
||||
if (categoriesToDisable.includes(option.value)) {
|
||||
option.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reorder meta boxes
|
||||
const sideSortables = document.querySelector('#side-sortables');
|
||||
if (sideSortables) {
|
||||
const typeDannonce = document.querySelector('#pods-meta-type-dannonce');
|
||||
const affichageAccueil = document.querySelector('#pods-meta-affichage-sur-laccueil');
|
||||
const thematique = document.querySelector('#pods-meta-thematique');
|
||||
if (typeDannonce) sideSortables.prepend(typeDannonce);
|
||||
if (affichageAccueil) sideSortables.appendChild(affichageAccueil);
|
||||
if (thematique) sideSortables.appendChild(thematique);
|
||||
}
|
||||
|
||||
const submitDiv = document.querySelector('#submitdiv');
|
||||
if (submitDiv && submitDiv.parentNode) {
|
||||
submitDiv.parentNode.appendChild(submitDiv);
|
||||
}
|
||||
|
||||
const champsContextuels = document.querySelector('#pods-meta-champs-contextuels');
|
||||
if (champsContextuels && champsContextuels.parentNode) {
|
||||
champsContextuels.parentNode.prepend(champsContextuels);
|
||||
}
|
||||
|
||||
initBodyLanguageTabs();
|
||||
initRefBibEditor();
|
||||
groupAxesCheckboxes();
|
||||
initAxesGroupObserver();
|
||||
updatePostboxVisibility();
|
||||
initDatePickerPopoverFix();
|
||||
initInfoPopovers();
|
||||
|
||||
// Place #pods-meta-documents-joints in #normal-sortables, right after
|
||||
// #pods-meta-champs-contextuels. This keeps it out of #post-body-content
|
||||
// (the body editor section) regardless of whether champsContextuels is
|
||||
// currently visible. When champsContextuels is hidden it takes no space,
|
||||
// so documentsJoints simply appears first in #normal-sortables.
|
||||
const documentsJoints = document.querySelector('#pods-meta-documents-joints');
|
||||
if (documentsJoints) {
|
||||
if (champsContextuels && champsContextuels.parentNode) {
|
||||
champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling);
|
||||
} else {
|
||||
const normalSortables = document.querySelector('#normal-sortables');
|
||||
if (normalSortables) normalSortables.prepend(documentsJoints);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject separator row for the Membres grid layout
|
||||
var membresTbody = document.querySelector('#pods-meta-membres .form-table tbody');
|
||||
if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) {
|
||||
var sep = document.createElement('tr');
|
||||
sep.className = 'membres-grid-separator';
|
||||
membresTbody.appendChild(sep);
|
||||
}
|
||||
updateMembresGridSeparator();
|
||||
}
|
||||
|
||||
var INFO_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 11v6"/><path d="M12 8v.01" stroke-width="2"/></svg>';
|
||||
var TRANSLATE_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
||||
|
||||
var TRANSLATE_LINES = [
|
||||
'Traduction en anglais apr\u00e8s //',
|
||||
'ex\u00a0: Texte en fran\u00e7ais // English text'
|
||||
];
|
||||
|
||||
// Tips without a `page` key default to 'post'.
|
||||
// type: 'translate' uses the globe icon + green button style.
|
||||
var INFO_TIPS = [
|
||||
// --- post edit page: info ---
|
||||
{
|
||||
selector: '.wp-heading-inline',
|
||||
lines: [
|
||||
'Saisir le titre anglais apr\u00e8s //',
|
||||
'ex\u00a0: Titre de l\u2019annonce // Title of the announcement'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-documents-joints .postbox-header h2',
|
||||
lines: [
|
||||
'Ajouter les images dans les documents.',
|
||||
'Ajouter les l\u00e9gendes comme titre du document.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-membres .postbox-header h2',
|
||||
lines: [
|
||||
'Le champ fonction change le libell\u00e9 de la liste de personnes cit\u00e9es.',
|
||||
'Le champ membre permet de lister les membres de Thalim li\u00e9s \u00e0 l\u2019annonce.',
|
||||
'Le champ autre personnes permet de lister des personnes ext\u00e9rieures \u00e0 Thalim.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-dates .postbox-header h2',
|
||||
lines: [
|
||||
'Pour entrer une date sans l\u2019heure, r\u00e9gler l\u2019heure sur 00\u202f:00.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2',
|
||||
lines: [
|
||||
'\u00c9pingler l\u2019annonce dans le diaporama la fait s\u2019afficher avant les autres.'
|
||||
]
|
||||
},
|
||||
{
|
||||
selector: '#pods-meta-medias .postbox-header h2',
|
||||
lines: [
|
||||
'Pour ajouter un m\u00e9dia Canal\u00a0U, copier le lien depuis \u00ab\u00a0Citer cette ressource\u00a0\u00bb.',
|
||||
'ex\u00a0: https://www.canal-u.tv/166564'
|
||||
]
|
||||
},
|
||||
// --- post edit page: translate ---
|
||||
{ type: 'translate', selector: '#pods-meta-documents-joints .postbox-header h2', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-sous-titre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-lieu th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-organisation th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-intervention th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-candidat th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-realisation th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-dirige th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-redaction th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-auteur th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-fonction-responsable th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-autre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-concerne th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-directeur th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-direction-d-ouvrage th',lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-intervenant th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-participants th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-type-autre th', lines: TRANSLATE_LINES },
|
||||
// --- contenu_general edit page: translate ---
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-umr th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-thalim th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', selector: '.pods-form-ui-row-name-siecles th', lines: TRANSLATE_LINES },
|
||||
// --- user/profile edit page: translate ---
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-4 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-1 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-2 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-3 th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affiliation-autre th', lines: TRANSLATE_LINES },
|
||||
{ type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-de-these th', lines: TRANSLATE_LINES },
|
||||
// --- taxonomy edit pages: translate ---
|
||||
{ type: 'translate', page: 'taxonomy', selector: 'label[for="name"]', lines: TRANSLATE_LINES },
|
||||
// --- user/profile edit page: info ---
|
||||
{
|
||||
page: 'user',
|
||||
selector: '.pods-form-ui-label-pods-meta-identifiant-hal',
|
||||
lines: [
|
||||
'Renseigner votre idHAL (en lettres), pas votre PersonId (en chiffres).'
|
||||
]
|
||||
},
|
||||
{
|
||||
page: 'user',
|
||||
selector: '.pods-form-ui-label-pods-meta-affichage-du-statut-1',
|
||||
lines: [
|
||||
'Texte de statut affiché sur le profil publique.'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var _popoverCloseHandlerRegistered = false;
|
||||
|
||||
function initInfoPopovers(currentPage) {
|
||||
currentPage = currentPage || 'post';
|
||||
|
||||
INFO_TIPS.forEach(function(tip) {
|
||||
if ((tip.page || 'post') !== currentPage) return;
|
||||
|
||||
var el = document.querySelector(tip.selector);
|
||||
if (!el) return;
|
||||
|
||||
var isTranslate = tip.type === 'translate';
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = isTranslate ? 'thalim-translate-btn' : 'thalim-info-btn';
|
||||
btn.setAttribute('aria-label', isTranslate ? 'Traduction bilingue' : 'Informations');
|
||||
btn.innerHTML = isTranslate ? TRANSLATE_ICON : INFO_ICON;
|
||||
|
||||
var popover = document.createElement('div');
|
||||
popover.className = 'thalim-info-popover' + (isTranslate ? ' thalim-translate-popover' : '');
|
||||
popover.innerHTML = tip.lines.map(function(line) {
|
||||
return '<p>' + line + '</p>';
|
||||
}).join('');
|
||||
|
||||
var wrapper = document.createElement('span');
|
||||
wrapper.className = 'thalim-info-wrapper';
|
||||
wrapper.appendChild(btn);
|
||||
wrapper.appendChild(popover);
|
||||
|
||||
el.appendChild(wrapper);
|
||||
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = popover.classList.contains('is-open');
|
||||
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
|
||||
p.classList.remove('is-open');
|
||||
});
|
||||
if (!isOpen) {
|
||||
var rect = btn.getBoundingClientRect();
|
||||
popover.style.top = (rect.bottom + 6) + 'px';
|
||||
popover.style.left = (rect.left + rect.width / 2) + 'px';
|
||||
popover.classList.add('is-open');
|
||||
}
|
||||
});
|
||||
|
||||
popover.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
|
||||
if (!_popoverCloseHandlerRegistered) {
|
||||
_popoverCloseHandlerRegistered = true;
|
||||
document.addEventListener('click', function() {
|
||||
document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
|
||||
p.classList.remove('is-open');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Only native WP field sections — never touch Pods tables (they may contain TinyMCE editors)
|
||||
var PROFILE_SECTION_KEYS = [
|
||||
'user-language-wrap',
|
||||
'user-first-name-wrap',
|
||||
'user-email-wrap',
|
||||
'user-pass1-wrap',
|
||||
'upload-avatar-row',
|
||||
];
|
||||
|
||||
// Desired order. Groups of 2 are wrapped in a flex row and displayed side by side.
|
||||
var PROFILE_ORDER = [
|
||||
['user-first-name-wrap', 'upload-avatar-row'],
|
||||
['user-email-wrap'],
|
||||
['user-language-wrap', 'user-pass1-wrap'],
|
||||
];
|
||||
|
||||
function reorderProfileSections() {
|
||||
var form = getProfileForm();
|
||||
if (!form) return;
|
||||
|
||||
var pairMap = {};
|
||||
|
||||
form.querySelectorAll('table.form-table').forEach(function(table) {
|
||||
PROFILE_SECTION_KEYS.forEach(function(key) {
|
||||
if (pairMap[key] || !table.querySelector('.' + key)) return;
|
||||
|
||||
// Find the associated heading: first try preceding sibling in same parent,
|
||||
// then look for an h2/h3 inside the same wrapper element.
|
||||
var h2 = null;
|
||||
var el = table.previousElementSibling;
|
||||
while (el) {
|
||||
if (el.tagName === 'H2' || el.tagName === 'H3') { h2 = el; break; }
|
||||
if (el.tagName === 'TABLE') break;
|
||||
el = el.previousElementSibling;
|
||||
}
|
||||
if (!h2 && table.parentElement !== form) {
|
||||
h2 = table.parentElement.querySelector('h2, h3');
|
||||
}
|
||||
|
||||
// The unit to move: if h2 and table share a non-form wrapper, move the wrapper.
|
||||
var wrapper = null;
|
||||
if (h2 && h2.parentElement !== form && h2.parentElement === table.parentElement) {
|
||||
wrapper = h2.parentElement;
|
||||
}
|
||||
|
||||
pairMap[key] = { h2: h2, table: table, wrapper: wrapper };
|
||||
});
|
||||
});
|
||||
|
||||
// Remove all matched units from DOM (dedup by actual element)
|
||||
var removed = new Set();
|
||||
function removeEl(el) {
|
||||
if (el && !removed.has(el)) { removed.add(el); el.remove(); }
|
||||
}
|
||||
Object.values(pairMap).forEach(function(unit) {
|
||||
if (unit.wrapper) { removeEl(unit.wrapper); }
|
||||
else { removeEl(unit.h2); removeEl(unit.table); }
|
||||
});
|
||||
|
||||
// Re-insert in declared order before the submit button
|
||||
var submitAnchor = form.querySelector('p.submit');
|
||||
function append(el) {
|
||||
if (submitAnchor && submitAnchor.parentNode) form.insertBefore(el, submitAnchor);
|
||||
else form.appendChild(el);
|
||||
}
|
||||
function appendUnit(unit) {
|
||||
if (unit.wrapper) { append(unit.wrapper); }
|
||||
else { if (unit.h2) append(unit.h2); append(unit.table); }
|
||||
}
|
||||
|
||||
PROFILE_ORDER.forEach(function(group) {
|
||||
var available = group.filter(function(key) { return !!pairMap[key]; });
|
||||
if (!available.length) return;
|
||||
|
||||
// Dedup: two keys may resolve to the same table/wrapper
|
||||
var seen = new Set();
|
||||
var units = [];
|
||||
available.forEach(function(key) {
|
||||
var unit = pairMap[key];
|
||||
var id = unit.wrapper || unit.table;
|
||||
if (!seen.has(id)) { seen.add(id); units.push(unit); }
|
||||
});
|
||||
|
||||
if (units.length === 1) {
|
||||
appendUnit(units[0]);
|
||||
} else {
|
||||
var row = document.createElement('div');
|
||||
row.className = 'profile-section-row';
|
||||
units.forEach(function(unit) {
|
||||
var col = document.createElement('div');
|
||||
col.className = 'profile-section-col';
|
||||
if (unit.wrapper) { col.appendChild(unit.wrapper); }
|
||||
else { if (unit.h2) col.appendChild(unit.h2); col.appendChild(unit.table); }
|
||||
row.appendChild(col);
|
||||
});
|
||||
append(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initProfileEditors() {
|
||||
reorderProfileSections();
|
||||
initInfoPopovers('user');
|
||||
|
||||
// Hide the "À propos du compte" section heading
|
||||
document.querySelectorAll('#your-profile h2, #adduser h2, #createuser h2').forEach(function(h2) {
|
||||
if (h2.textContent.trim() === '\u00c0 propos du compte') {
|
||||
h2.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Rename "Rôle" label to "Rôle sur le site"
|
||||
var roleLabel = document.querySelector('label[for="role"]');
|
||||
if (roleLabel && roleLabel.textContent.trim() === 'R\u00f4le') {
|
||||
roleLabel.textContent = 'R\u00f4le sur le site';
|
||||
}
|
||||
}
|
||||
|
||||
// Gutenberg's Popover component closes on outside click via focusout detection.
|
||||
// But if focus never enters the popover, focusout never fires and clicking outside
|
||||
// does nothing. Fix: focus the popover container as soon as it appears in the DOM.
|
||||
function initDatePickerPopoverFix() {
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var added = mutations[i].addedNodes;
|
||||
for (var j = 0; j < added.length; j++) {
|
||||
var node = added[j];
|
||||
if (node.nodeType !== 1) continue;
|
||||
var content = node.classList.contains('components-popover__content')
|
||||
? node
|
||||
: node.querySelector && node.querySelector('.components-popover__content');
|
||||
if (content) {
|
||||
var c = content;
|
||||
requestAnimationFrame(function() {
|
||||
if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1');
|
||||
c.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function updateMembresGridSeparator() {
|
||||
var sep = document.querySelector('#pods-meta-membres .membres-grid-separator');
|
||||
if (!sep) return;
|
||||
var autreRows = document.querySelectorAll('#pods-meta-membres [class*="pods-form-ui-row-name-autre-"]');
|
||||
var anyVisible = Array.from(autreRows).some(function(row) {
|
||||
return row.style.display !== 'none';
|
||||
});
|
||||
sep.style.display = anyVisible ? '' : 'none';
|
||||
}
|
||||
|
||||
// Inject a "Type de programme" filter select into the taxonomy search form.
|
||||
// The form already has hidden taxonomy/post_type fields so the select value
|
||||
// is submitted with them and picked up by pre_get_terms server-side.
|
||||
function initProgrammeFilter() {
|
||||
var form = document.querySelector('form.search-form');
|
||||
if (!form) return;
|
||||
|
||||
var types = [
|
||||
'Programme subventionné',
|
||||
'Autre programme',
|
||||
'Ancien programme'
|
||||
];
|
||||
|
||||
// Read current filter value from the URL.
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var current = params.get('type_de_programme') || '';
|
||||
|
||||
var select = document.createElement('select');
|
||||
select.name = 'type_de_programme';
|
||||
select.id = 'filter-type-de-programme';
|
||||
select.style.cssText = 'margin-right:6px;';
|
||||
|
||||
var blank = document.createElement('option');
|
||||
blank.value = '';
|
||||
blank.textContent = 'Tous les types';
|
||||
select.appendChild(blank);
|
||||
|
||||
types.forEach(function(type) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = type;
|
||||
opt.textContent = type;
|
||||
if (type === current) opt.selected = true;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
|
||||
// Insert before the first <p> (search-box) inside the form.
|
||||
var searchBox = form.querySelector('p.search-box');
|
||||
form.insertBefore(select, searchBox || null);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
renameArticlesToAnnonces();
|
||||
|
||||
if (isPostEditPage()) {
|
||||
setupBodyTabsDom();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (isPostEditPage()) {
|
||||
initPostEditPage();
|
||||
}
|
||||
if (isProfileEditPage()) {
|
||||
initProfileEditors();
|
||||
}
|
||||
var TRANSLATE_TAXONOMIES = ['axe_thematique', 'programme_de_recherche', 'post_tag'];
|
||||
var isTaxonomyListPage = TRANSLATE_TAXONOMIES.some(function(tax) {
|
||||
return window.location.search.indexOf('taxonomy=' + tax) !== -1;
|
||||
});
|
||||
if (isTaxonomyListPage) {
|
||||
initInfoPopovers('taxonomy');
|
||||
}
|
||||
if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) {
|
||||
initProgrammeFilter();
|
||||
}
|
||||
document.body.classList.add('admin-mods-ready');
|
||||
}, 100);
|
||||
|
||||
// Fallback: force reveal after 2s in case the 100ms path failed (e.g. JS error mid-init)
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('admin-mods-ready');
|
||||
}, 2000);
|
||||
|
||||
$('#pods-form-ui-pods-meta-categorie').change(function() {
|
||||
setTimeout(function() {
|
||||
updatePostboxVisibility();
|
||||
updateMembresGridSeparator();
|
||||
}, 10);
|
||||
});
|
||||
|
||||
if (isProfileEditPage() || window.pagenow === 'edit-tags' || window.pagenow === 'term') {
|
||||
$(window).on('load', function() {
|
||||
var scope = getProfileForm() || document;
|
||||
scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
|
||||
if (!ta.id) return;
|
||||
ensureVisualMode(ta.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (isPodsModal()) {
|
||||
$(window).on('load', function() {
|
||||
var itemId = $('#post_ID').val();
|
||||
if (window.PodsDFV && itemId) {
|
||||
window.PodsDFV.setFieldValue('post', itemId, 'categorie', '12', 0);
|
||||
}
|
||||
|
||||
// Lock category select to 12 in iframe — delay to run after Pods React re-render
|
||||
setTimeout(function() {
|
||||
var $select = $('#pods-form-ui-pods-meta-categorie');
|
||||
if ($select.length) {
|
||||
$select.find('option').each(function() {
|
||||
this.disabled = this.value !== '12';
|
||||
});
|
||||
$select.val('12');
|
||||
}
|
||||
updatePostboxVisibility();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
119
js/adminFormRestore.js
Normal file
119
js/adminFormRestore.js
Normal file
@@ -0,0 +1,119 @@
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_PREFIX = 'thalim_form_restore_';
|
||||
var MAX_AGE_MS = 10 * 60 * 1000; // 10 min
|
||||
|
||||
function getPostId() {
|
||||
return $('#post_ID').val() || 'new';
|
||||
}
|
||||
|
||||
function getStorageKey() {
|
||||
return STORAGE_PREFIX + getPostId();
|
||||
}
|
||||
|
||||
function saveFormData() {
|
||||
var data = { timestamp: Date.now() };
|
||||
|
||||
var $title = $('#title');
|
||||
if ($title.length) data.title = $title.val();
|
||||
|
||||
var $content = $('#content');
|
||||
if ($content.length) {
|
||||
if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) {
|
||||
data.content = tinyMCE.get('content').getContent();
|
||||
} else {
|
||||
data.content = $content.val();
|
||||
}
|
||||
}
|
||||
|
||||
$('[name^="pods_meta_"]').each(function() {
|
||||
data[this.name] = $(this).val();
|
||||
});
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(getStorageKey(), JSON.stringify(data));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function restoreFormData() {
|
||||
var navEntries = performance.getEntriesByType('navigation');
|
||||
if (!navEntries.length || navEntries[0].type !== 'back_forward') return;
|
||||
|
||||
var key = getStorageKey();
|
||||
var stored, data;
|
||||
try {
|
||||
stored = sessionStorage.getItem(key);
|
||||
if (!stored) return;
|
||||
data = JSON.parse(stored);
|
||||
} catch (e) { return; }
|
||||
|
||||
if (!data.timestamp || Date.now() - data.timestamp > MAX_AGE_MS) {
|
||||
try { sessionStorage.removeItem(key); } catch (e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// Restaurer titre
|
||||
if (data.title !== undefined) $('#title').val(data.title);
|
||||
|
||||
// Restaurer contenu (TinyMCE ou textarea brut)
|
||||
if (data.content !== undefined) {
|
||||
$('#content').val(data.content);
|
||||
if (typeof tinyMCE !== 'undefined') {
|
||||
var ed = tinyMCE.get('content');
|
||||
if (ed) {
|
||||
ed.setContent(data.content);
|
||||
} else {
|
||||
tinyMCE.on('AddEditor', function(e) {
|
||||
if (e.editor.id === 'content') {
|
||||
e.editor.on('init', function() {
|
||||
e.editor.setContent(data.content);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restaurer champs Pods après rendu React
|
||||
var restorePods = function() {
|
||||
setTimeout(function() {
|
||||
var postId = getPostId();
|
||||
Object.keys(data).forEach(function(name) {
|
||||
if (!name.startsWith('pods_meta_')) return;
|
||||
var fieldName = name.replace(/^pods_meta_/, '');
|
||||
var value = data[name];
|
||||
var $el = $('[name="' + name + '"]');
|
||||
if ($el.length) $el.val(value).trigger('change');
|
||||
if (window.PodsDFV && postId) {
|
||||
try { window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0); } catch (e) {}
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
restorePods();
|
||||
} else {
|
||||
$(window).on('load', restorePods);
|
||||
}
|
||||
|
||||
// Notice informative
|
||||
var $notice = $('<div class="notice notice-info is-dismissible"><p>Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.</p></div>');
|
||||
$('#wpbody-content').find('.wrap').first().find('h1').after($notice);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if (window.pagenow !== 'post' && window.pagenow !== 'post-new') return;
|
||||
|
||||
// Nettoyage si sauvegarde réussie
|
||||
if ($('.notice-success, #message.updated').length) {
|
||||
try { sessionStorage.removeItem(getStorageKey()); } catch (e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
$('#post').on('submit', saveFormData);
|
||||
restoreFormData();
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
146
js/agendaView.js
Normal file
146
js/agendaView.js
Normal file
@@ -0,0 +1,146 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var toggleBtn = document.querySelector('.agenda-toggle-btn');
|
||||
var gridSections = document.getElementById('grid-sections');
|
||||
var agendaEl = document.getElementById('agenda-view');
|
||||
if (!toggleBtn || !agendaEl) return;
|
||||
|
||||
var swiper = null;
|
||||
var agendaPage = 0;
|
||||
var agendaDone = false;
|
||||
var agendaLoading = false;
|
||||
var swiperWrapper = document.getElementById('agenda-swiper-wrapper');
|
||||
var agendaSpinner = document.getElementById('agenda-spinner');
|
||||
|
||||
var categoryId = agendaEl.dataset.category || '';
|
||||
var includeChildren = agendaEl.dataset.includeChildren || '0';
|
||||
var axe = agendaEl.dataset.axe || '';
|
||||
var dateFrom = agendaEl.dataset.dateFrom || '';
|
||||
var dateTo = agendaEl.dataset.dateTo || '';
|
||||
|
||||
function showAgenda() {
|
||||
if (gridSections) gridSections.style.display = 'none';
|
||||
agendaEl.classList.add('is-active');
|
||||
toggleBtn.innerHTML =
|
||||
'<i class="iconoir-view-grid"></i>' +
|
||||
(agendaViewData.lang === 'en' ? 'Switch to grid view' : 'Passer à la vue grille');
|
||||
if (!swiper) {
|
||||
loadMoreAgenda(function (data) {
|
||||
var todayOffset = Math.max(0, ((data && data.today_offset) || 0) - 1);
|
||||
var anchorPage = Math.ceil((todayOffset + 1) / 12);
|
||||
if (anchorPage > 1) {
|
||||
chainLoadPages(2, anchorPage, function () {
|
||||
initSwiper(todayOffset);
|
||||
});
|
||||
} else {
|
||||
initSwiper(todayOffset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showGrid() {
|
||||
agendaEl.classList.remove('is-active');
|
||||
if (gridSections) gridSections.style.display = '';
|
||||
toggleBtn.innerHTML =
|
||||
'<i class="iconoir-calendar"></i>' +
|
||||
(agendaViewData.lang === 'en' ? 'Switch to agenda view' : 'Passer à la vue agenda');
|
||||
}
|
||||
|
||||
function initSwiper(initialSlide) {
|
||||
swiper = new Swiper(agendaEl.querySelector('.agenda-swiper'), {
|
||||
slidesPerView: 1.2,
|
||||
spaceBetween: 20,
|
||||
initialSlide: initialSlide || 0,
|
||||
navigation: {
|
||||
nextEl: agendaEl.querySelector('.agenda-swiper-prev'),
|
||||
prevEl: agendaEl.querySelector('.agenda-swiper-next'),
|
||||
},
|
||||
breakpoints: {
|
||||
640: { slidesPerView: 2, spaceBetween: 24 },
|
||||
1024: { slidesPerView: 3, spaceBetween: 32 },
|
||||
},
|
||||
on: {
|
||||
reachEnd: function () {
|
||||
if (!agendaDone) loadMoreAgenda();
|
||||
},
|
||||
},
|
||||
});
|
||||
swiper.changeLanguageDirection('rtl');
|
||||
}
|
||||
|
||||
function chainLoadPages(fromPage, toPage, callback) {
|
||||
if (fromPage > toPage) { callback(); return; }
|
||||
loadMoreAgenda(function () {
|
||||
chainLoadPages(fromPage + 1, toPage, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreAgenda(callback) {
|
||||
if (agendaLoading || agendaDone) return;
|
||||
agendaLoading = true;
|
||||
agendaPage++;
|
||||
agendaSpinner.style.display = 'flex';
|
||||
|
||||
var data = new FormData();
|
||||
data.append('action', 'load_more_agenda');
|
||||
data.append('page', agendaPage);
|
||||
data.append('nonce', agendaViewData.nonce);
|
||||
data.append('lang', agendaViewData.lang);
|
||||
if (categoryId) data.append('category', categoryId);
|
||||
if (includeChildren==='1') data.append('include_children', '1');
|
||||
if (axe) data.append('axe', axe);
|
||||
if (dateFrom) data.append('date_from', dateFrom);
|
||||
if (dateTo) data.append('date_to', dateTo);
|
||||
|
||||
fetch(agendaViewData.ajaxUrl, { method: 'POST', body: data })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (result) {
|
||||
agendaSpinner.style.display = 'none';
|
||||
agendaLoading = false;
|
||||
if (result.success && result.data.html) {
|
||||
var tmp = document.createElement('div');
|
||||
tmp.innerHTML = result.data.html;
|
||||
Array.from(tmp.children).forEach(function (slide) {
|
||||
if (swiper) {
|
||||
swiper.appendSlide(slide.outerHTML);
|
||||
} else {
|
||||
swiperWrapper.appendChild(slide);
|
||||
}
|
||||
});
|
||||
if (swiper) swiper.update();
|
||||
if (callback) callback(result.data);
|
||||
} else {
|
||||
agendaDone = true;
|
||||
if (callback) callback(result.data);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
agendaSpinner.style.display = 'none';
|
||||
agendaLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
toggleBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
if (agendaEl.classList.contains('is-active')) {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.delete('view');
|
||||
history.pushState({}, '', url.toString());
|
||||
showGrid();
|
||||
} else {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('view', 'agenda');
|
||||
history.pushState({}, '', url.toString());
|
||||
showAgenda();
|
||||
}
|
||||
});
|
||||
|
||||
// Initial state driven by server-side class
|
||||
if (agendaEl.classList.contains('is-active')) {
|
||||
showAgenda();
|
||||
}
|
||||
});
|
||||
}());
|
||||
577
js/animatedLogo.js
Normal file
577
js/animatedLogo.js
Normal file
@@ -0,0 +1,577 @@
|
||||
const CURSOR_INFLUENCE_INNER = 150;
|
||||
const CURSOR_INFLUENCE_OUTER = 300;
|
||||
const EASING_FACTOR = 0.03;
|
||||
|
||||
const COLORS = [
|
||||
'#e0775d',
|
||||
'#7cc0c6',
|
||||
'#e05680',
|
||||
'#46ae51',
|
||||
'#bb8dd9',
|
||||
'#f7ff29',
|
||||
];
|
||||
|
||||
class FloatingShape {
|
||||
constructor(element, originalX, originalY, width, height) {
|
||||
this.element = element;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.originalX = originalX;
|
||||
this.originalY = originalY;
|
||||
this.posX = originalX;
|
||||
this.posY = originalY;
|
||||
this.targetX = originalX;
|
||||
this.targetY = originalY;
|
||||
}
|
||||
|
||||
update(mouseX, mouseY) {
|
||||
const shapeCenterX = this.originalX + this.width / 2;
|
||||
const shapeCenterY = this.originalY + this.height / 2;
|
||||
|
||||
const dx = mouseX - shapeCenterX;
|
||||
const dy = mouseY - shapeCenterY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
const innerRadius = CURSOR_INFLUENCE_INNER / 2;
|
||||
const outerRadius = CURSOR_INFLUENCE_OUTER / 2;
|
||||
|
||||
if (distance < outerRadius && distance > 0) {
|
||||
const dirX = dx / distance;
|
||||
const dirY = dy / distance;
|
||||
|
||||
let strength, maxDisplacement;
|
||||
|
||||
if (distance < innerRadius) {
|
||||
strength = (innerRadius - distance) / innerRadius;
|
||||
maxDisplacement = innerRadius;
|
||||
} else {
|
||||
const outerZoneProgress = (outerRadius - distance) / (outerRadius - innerRadius);
|
||||
strength = outerZoneProgress * 0.5;
|
||||
maxDisplacement = innerRadius * 0.6;
|
||||
}
|
||||
|
||||
this.targetX = this.originalX - dirX * strength * maxDisplacement;
|
||||
this.targetY = this.originalY - dirY * strength * maxDisplacement;
|
||||
} else {
|
||||
this.targetX = this.originalX;
|
||||
this.targetY = this.originalY;
|
||||
}
|
||||
|
||||
this.posX += (this.targetX - this.posX) * EASING_FACTOR;
|
||||
this.posY += (this.targetY - this.posY) * EASING_FACTOR;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.element.style.transform = `translate3d(${this.posX}px, ${this.posY}px, 0)`;
|
||||
}
|
||||
}
|
||||
|
||||
class FloatingShapesManager {
|
||||
constructor(containerId) {
|
||||
this.container = document.getElementById(containerId);
|
||||
if (!this.container) {
|
||||
console.error(`Container #${containerId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.shapes = [];
|
||||
this.strokePaths = []; // Store all stroke paths for fill control
|
||||
this.fillShape = null; // Reference to the filled shape SVG for fade control
|
||||
this.fillShapeReady = false; // Track if fillshape animation has completed
|
||||
this.thalimText = null; // Reference to the THALIM text element
|
||||
this.textReady = false; // Track if text animation has completed
|
||||
this.mouseX = 0;
|
||||
this.mouseY = 0;
|
||||
this.animationId = null;
|
||||
this.isTouching = false; // Track if currently in a touch interaction
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const containerRect = this.container.getBoundingClientRect();
|
||||
const containerWidth = containerRect.width;
|
||||
const containerHeight = containerRect.height;
|
||||
|
||||
const shapeConfigs = [
|
||||
{
|
||||
id: 'shape-1',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape1.svg`,
|
||||
baseWidth: 53.564522,
|
||||
baseHeight: 112.37409,
|
||||
scale: 1.5,
|
||||
posX: this.isMobile() ? 20: 35,
|
||||
posY: -20,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 1.9,
|
||||
gradientStart: COLORS[0], // orange
|
||||
gradientEnd: '#e0b7ad'
|
||||
},
|
||||
{
|
||||
id: 'shape-2',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape4.svg`,
|
||||
baseWidth: 74.08564,
|
||||
baseHeight: 121.90051,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: this.isMobile() ? -8 : -5,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 2.5,
|
||||
gradientStart: '#aec4c6',
|
||||
gradientEnd: COLORS[1] // blue
|
||||
},
|
||||
{
|
||||
id: 'shape-3',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape3.svg`,
|
||||
baseWidth: 159.16571,
|
||||
baseHeight: 87.756729,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: -10,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 3.1,
|
||||
gradientStart: COLORS[2], // pink
|
||||
gradientEnd: '#e0b0be'
|
||||
},
|
||||
{
|
||||
id: 'shape-4',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape2.svg`,
|
||||
baseWidth: 143.26076,
|
||||
baseHeight: 20.26207,
|
||||
scale: 1.5,
|
||||
posX: 0,
|
||||
posY: this.isMobile() ? 20 : 45,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 3.7,
|
||||
gradientStart: '#8bc491',
|
||||
gradientEnd: COLORS[3] // green
|
||||
},
|
||||
{
|
||||
id: 'shape-5',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/shape5.svg`,
|
||||
baseWidth: 155.66518,
|
||||
baseHeight: 87.785599,
|
||||
scale: 1.5,
|
||||
posX: this.isMobile() ? 13 : 19.5,
|
||||
posY: this.isMobile() ? -18 : -23.5,
|
||||
strokeWidth: 2,
|
||||
animationDuration: 4.3,
|
||||
gradientStart: COLORS[4], // purple
|
||||
gradientEnd: '#c9b0d9'
|
||||
},
|
||||
{
|
||||
id: 'fillshape',
|
||||
svgPath: `${themeDirURI}/assets/logo-shapes/fillshape.svg`,
|
||||
baseWidth: 142.78297,
|
||||
baseHeight: 72.903015,
|
||||
scale: this.isMobile() ? 0.78 : 1.47,
|
||||
posX: this.isMobile() ? 7.5 : 11,
|
||||
posY: this.isMobile() ? -13 : -14.5,
|
||||
isFilled: true,
|
||||
targetOpacity: 1,
|
||||
animationDuration: 0.9,
|
||||
animationDelay: 3.7 // Start after longest stroke animation
|
||||
}
|
||||
];
|
||||
|
||||
// Create shapes with center-based positioning
|
||||
const centerX = containerWidth / 2;
|
||||
const centerY = containerHeight / 2;
|
||||
|
||||
// Apply mobile scale on smaller viewports
|
||||
const mobileScale = 0.8;
|
||||
shapeConfigs.forEach((config) => {
|
||||
const scale = config.id === 'fillshape' ? config.scale : this.isMobile() ? mobileScale : config.scale;
|
||||
const scaledWidth = config.baseWidth * scale;
|
||||
const scaledHeight = config.baseHeight * scale;
|
||||
|
||||
// Calculate position from center with offset, minus half dimensions to center the shape
|
||||
const x = centerX + (config.posX || 0) - scaledWidth / 2;
|
||||
const y = centerY + (config.posY || 0) - scaledHeight / 2;
|
||||
|
||||
// Create animated stroke element
|
||||
this.createAnimatedStrokeElement(config, scaledWidth, scaledHeight, x, y);
|
||||
});
|
||||
|
||||
// Create THALIM text overlay
|
||||
this.createThalimText(centerX, centerY);
|
||||
|
||||
this.container.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||
this.container.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
|
||||
this.container.addEventListener('touchstart', this.handleTouchStart.bind(this));
|
||||
this.container.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
|
||||
this.container.addEventListener('touchend', this.handleTouchEnd.bind(this));
|
||||
window.addEventListener('resize', this.handleResize.bind(this));
|
||||
|
||||
// Set sketch margin on mobile based on hero-logos height
|
||||
this.setSketchMarginOnMobile();
|
||||
|
||||
this.animate();
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
return window.innerWidth < 768; // tablet breakpoint from scss/_variables.scss
|
||||
}
|
||||
|
||||
setSketchMarginOnMobile() {
|
||||
if (this.isMobile()) {
|
||||
const heroLogos = document.querySelector('.hero-logos');
|
||||
if (heroLogos) {
|
||||
const logoHeight = heroLogos.offsetHeight;
|
||||
this.container.style.marginTop = `${logoHeight}px`;
|
||||
}
|
||||
} else {
|
||||
// Reset margin-top on desktop (handled by CSS)
|
||||
this.container.style.marginTop = '';
|
||||
}
|
||||
}
|
||||
|
||||
async createAnimatedStrokeElement(config, width, height, x, y) {
|
||||
try {
|
||||
const response = await fetch(config.svgPath);
|
||||
const svgText = await response.text();
|
||||
|
||||
const parser = new DOMParser();
|
||||
const svgDoc = parser.parseFromString(svgText, 'image/svg+xml');
|
||||
const svgElement = svgDoc.querySelector('svg');
|
||||
|
||||
if (!svgElement) {
|
||||
console.error(`Failed to parse SVG for ${config.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create shape div for physics
|
||||
const shapeDiv = document.createElement('div');
|
||||
shapeDiv.className = 'floating-shape';
|
||||
shapeDiv.id = config.id;
|
||||
shapeDiv.style.zIndex = config.isFilled ? '10' : '1'; // Fillshape above strokes
|
||||
|
||||
// Set SVG dimensions
|
||||
svgElement.setAttribute('width', width);
|
||||
svgElement.setAttribute('height', height);
|
||||
|
||||
if (config.isFilled) {
|
||||
// Handle filled shapes (fade-in animation)
|
||||
svgElement.style.opacity = '0';
|
||||
svgElement.style.transition = 'opacity 0.3s ease-out'; // Smooth fade on mouse interaction
|
||||
const duration = config.animationDuration || 1.5;
|
||||
const delay = config.animationDelay || 0;
|
||||
const targetOpacity = config.targetOpacity || 0.5;
|
||||
|
||||
svgElement.style.setProperty('--target-opacity', targetOpacity);
|
||||
svgElement.style.animation = `fadeIn ${duration}s ease-in-out ${delay}s forwards`;
|
||||
|
||||
// Remove animation after it completes so we can control opacity manually
|
||||
setTimeout(() => {
|
||||
svgElement.style.animation = 'none';
|
||||
svgElement.style.opacity = targetOpacity;
|
||||
this.fillShapeReady = true; // Mark fillshape as ready for interaction
|
||||
}, (delay + duration) * 1000);
|
||||
|
||||
// Store reference for mouse interaction
|
||||
this.fillShape = svgElement;
|
||||
this.fillShapeOpacity = targetOpacity;
|
||||
|
||||
// Position the fillshape div (since it's not in the physics system)
|
||||
shapeDiv.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
||||
} else {
|
||||
// Handle stroked shapes (gradient + stroke drawing animation)
|
||||
// Create gradient for stroke
|
||||
const gradientId = `gradient-${config.id}`;
|
||||
|
||||
// Create defs element if it doesn't exist
|
||||
let defs = svgElement.querySelector('defs');
|
||||
if (!defs) {
|
||||
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
svgElement.insertBefore(defs, svgElement.firstChild);
|
||||
}
|
||||
|
||||
// Create linear gradient
|
||||
const gradient = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
||||
gradient.setAttribute('id', gradientId);
|
||||
gradient.setAttribute('x1', '0%');
|
||||
gradient.setAttribute('y1', '0%');
|
||||
gradient.setAttribute('x2', '100%');
|
||||
gradient.setAttribute('y2', '100%');
|
||||
|
||||
// Create gradient stops
|
||||
const stop1 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop1.setAttribute('offset', '0%');
|
||||
stop1.setAttribute('stop-color', config.gradientStart);
|
||||
|
||||
const stop2 = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
|
||||
stop2.setAttribute('offset', '100%');
|
||||
stop2.setAttribute('stop-color', config.gradientEnd);
|
||||
|
||||
gradient.appendChild(stop1);
|
||||
gradient.appendChild(stop2);
|
||||
defs.appendChild(gradient);
|
||||
|
||||
// Apply stroke styling and animation to all paths in the SVG
|
||||
const paths = svgElement.querySelectorAll('path, polyline, polygon, line, circle, ellipse, rect');
|
||||
paths.forEach(path => {
|
||||
// Set stroke properties
|
||||
path.style.fill = 'white';
|
||||
path.style.fillOpacity = '0'; // Start invisible
|
||||
path.style.transition = 'fill-opacity 0.5s ease-in-out'; // Smooth fill changes
|
||||
path.style.stroke = `url(#${gradientId})`;
|
||||
// Don't override stroke-width - preserve SVG's compensated values
|
||||
path.style.strokeLinecap = 'round';
|
||||
path.style.strokeLinejoin = 'round';
|
||||
|
||||
// Calculate path length for animation
|
||||
const pathLength = path.getTotalLength ? path.getTotalLength() : 1000;
|
||||
|
||||
// Set CSS variable for path length
|
||||
path.style.setProperty('--path-length', pathLength);
|
||||
|
||||
// Set up stroke dash animation
|
||||
path.style.strokeDasharray = pathLength;
|
||||
path.style.strokeDashoffset = pathLength;
|
||||
|
||||
// Create CSS animation (plays once and stays completed)
|
||||
const duration = config.animationDuration || 3;
|
||||
path.style.animation = `drawStroke ${duration}s ease-in-out forwards`;
|
||||
|
||||
// Store path reference for fill control
|
||||
this.strokePaths.push(path);
|
||||
});
|
||||
|
||||
// Fade in white fill when text starts appearing (at 4.3s)
|
||||
setTimeout(() => {
|
||||
paths.forEach(path => {
|
||||
path.style.fillOpacity = '0.7';
|
||||
});
|
||||
}, 4300);
|
||||
}
|
||||
|
||||
// Append SVG to shape div
|
||||
shapeDiv.appendChild(svgElement);
|
||||
this.container.appendChild(shapeDiv);
|
||||
|
||||
// Create FloatingShape instance (only for shapes with physics, not filled shapes)
|
||||
if (!config.isFilled) {
|
||||
const floatingShape = new FloatingShape(shapeDiv, x, y, width, height);
|
||||
this.shapes.push(floatingShape);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error loading SVG for ${config.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
createThalimText(centerX, centerY) {
|
||||
|
||||
// Create container for the text
|
||||
const textContainer = document.createElement('div');
|
||||
textContainer.className = 'thalim-text';
|
||||
textContainer.style.top = '-20px';
|
||||
textContainer.style.left = '10px';
|
||||
textContainer.style.transform = `translate3d(${centerX}px, ${centerY}px, 0) translate(-50%, -50%)`; // Center the text
|
||||
textContainer.style.color = '#000000cc';
|
||||
|
||||
// Create individual letter spans
|
||||
const letters = 'thalim'.split('');
|
||||
letters.forEach((letter, index) => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = letter;
|
||||
span.style.opacity = '0';
|
||||
span.style.animation = `letterAppear 0.8s ease-in-out ${4.2 + index * 0.1}s forwards`;
|
||||
textContainer.appendChild(span);
|
||||
});
|
||||
|
||||
// Mark text as ready after last letter finishes animating
|
||||
// Last letter: 4.2s + 0.5s (6th letter) + 0.8s (duration) = 5.5s
|
||||
setTimeout(() => {
|
||||
this.textReady = true;
|
||||
}, 5500);
|
||||
|
||||
this.container.appendChild(textContainer);
|
||||
this.thalimText = textContainer;
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
// Ignore mouse events during touch interactions (prevents interference from synthetic mouse events)
|
||||
if (this.isTouching) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
this.mouseX = e.clientX - rect.left;
|
||||
this.mouseY = e.clientY - rect.top;
|
||||
|
||||
// Fade out fillshape and text on mouse move (only if they've finished appearing)
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
this.fillShape.style.opacity = '0';
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
this.thalimText.style.opacity = '0';
|
||||
}
|
||||
// Fade out white fills on stroke shapes
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0';
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseLeave() {
|
||||
// Move mouse far away to trigger return animation
|
||||
this.mouseX = -10000;
|
||||
this.mouseY = -10000;
|
||||
|
||||
// Fade fillshape and text back in after delay (let strokes settle first)
|
||||
// Only if they have completed their initial animations
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
setTimeout(() => {
|
||||
this.fillShape.style.opacity = this.fillShapeOpacity;
|
||||
}, 800); // Delay to allow strokes to return to position
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
setTimeout(() => {
|
||||
this.thalimText.style.opacity = '1';
|
||||
}, 800);
|
||||
}
|
||||
// Fade white fills back in on stroke shapes
|
||||
setTimeout(() => {
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0.7';
|
||||
});
|
||||
}, 800);
|
||||
}
|
||||
|
||||
handleTouchStart() {
|
||||
// Mark that we're in a touch interaction to prevent mouse event interference
|
||||
this.isTouching = true;
|
||||
}
|
||||
|
||||
handleTouchMove(e) {
|
||||
e.preventDefault(); // Prevent scrolling while interacting with sketch
|
||||
const rect = this.container.getBoundingClientRect();
|
||||
const touch = e.touches[0];
|
||||
this.mouseX = touch.clientX - rect.left;
|
||||
this.mouseY = touch.clientY - rect.top;
|
||||
|
||||
// Fade out fillshape and text on touch move (only if they've finished appearing)
|
||||
if (this.fillShape && this.fillShapeReady) {
|
||||
this.fillShape.style.opacity = '0';
|
||||
}
|
||||
if (this.thalimText && this.textReady) {
|
||||
this.thalimText.style.opacity = '0';
|
||||
}
|
||||
// Fade out white fills on stroke shapes
|
||||
this.strokePaths.forEach(path => {
|
||||
path.style.fillOpacity = '0';
|
||||
});
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
// Reset shapes to original position
|
||||
this.handleMouseLeave();
|
||||
|
||||
// Clear touch flag after a delay to ignore synthetic mouse events
|
||||
setTimeout(() => {
|
||||
this.isTouching = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
// Don't refresh on mobile (prevents refresh during scroll when browser bar appears/disappears)
|
||||
if (this.isMobile()) {
|
||||
// Still update margin-top on mobile if needed
|
||||
this.setSketchMarginOnMobile();
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounced resize: only recreate after user stops resizing for 250ms
|
||||
// This prevents page crashes from rapid resize events
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
// Stop animation loop before destroying
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
|
||||
// Clear shapes and container
|
||||
this.shapes = [];
|
||||
this.container.innerHTML = '';
|
||||
|
||||
// Reinitialize
|
||||
this.init();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
animate() {
|
||||
// Update all shapes
|
||||
this.shapes.forEach(shape => {
|
||||
shape.update(this.mouseX, this.mouseY);
|
||||
shape.render();
|
||||
});
|
||||
|
||||
// Continue loop
|
||||
this.animationId = requestAnimationFrame(this.animate.bind(this));
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
this.container.removeEventListener('mousemove', this.handleMouseMove);
|
||||
this.container.removeEventListener('mouseleave', this.handleMouseLeave);
|
||||
this.container.removeEventListener('touchstart', this.handleTouchStart);
|
||||
this.container.removeEventListener('touchmove', this.handleTouchMove);
|
||||
this.container.removeEventListener('touchend', this.handleTouchEnd);
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject CSS for stroke and fade animations
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes drawStroke {
|
||||
0% {
|
||||
stroke-dashoffset: var(--path-length);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: var(--target-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes letterAppear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.floatingShapes = new FloatingShapesManager('sketch');
|
||||
});
|
||||
} else {
|
||||
window.floatingShapes = new FloatingShapesManager('sketch');
|
||||
}
|
||||
28
js/annoncesSwiper.js
Normal file
28
js/annoncesSwiper.js
Normal file
@@ -0,0 +1,28 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-swiper]').forEach(function (section) {
|
||||
new Swiper(section.querySelector('.swiper'), {
|
||||
navigation: {
|
||||
addIcons: false,
|
||||
nextEl: section.querySelector('.swiper-button-next'),
|
||||
prevEl: section.querySelector('.swiper-button-prev'),
|
||||
},
|
||||
autoplay: {
|
||||
delay: 5000,
|
||||
disableOnInteraction: false,
|
||||
pauseOnMouseEnter: true,
|
||||
},
|
||||
slidesPerView: 1,
|
||||
spaceBetween: 30,
|
||||
breakpoints: {
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
spaceBetween: 40,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 3,
|
||||
spaceBetween: 50,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
138
js/categoryFilters.js
Normal file
138
js/categoryFilters.js
Normal file
@@ -0,0 +1,138 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// ── Filters toggle ────────────────────────────────────────
|
||||
var toggleBtn = document.getElementById('category-filters-toggle');
|
||||
var filtersEl = document.getElementById('category-filters');
|
||||
|
||||
if (toggleBtn && filtersEl) {
|
||||
toggleBtn.addEventListener('click', function () {
|
||||
var isOpen = filtersEl.classList.toggle('is-open');
|
||||
toggleBtn.classList.toggle('is-open', isOpen);
|
||||
toggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
var dateBtn = document.getElementById('filter-date-btn');
|
||||
var datePopover = document.getElementById('filter-date-popover');
|
||||
var dateFrom = document.getElementById('filter-date-from');
|
||||
var dateTo = document.getElementById('filter-date-to');
|
||||
var dateApply = document.getElementById('filter-date-apply');
|
||||
var axeBtn = document.getElementById('filter-axe-btn');
|
||||
var axePopover = document.getElementById('filter-axe-popover');
|
||||
|
||||
if (!dateBtn) return;
|
||||
|
||||
// Build URL with updated query params
|
||||
function buildUrl(params) {
|
||||
var url = new URL(window.location.href);
|
||||
Object.keys(params).forEach(function (key) {
|
||||
if (params[key]) {
|
||||
url.searchParams.set(key, params[key]);
|
||||
} else {
|
||||
url.searchParams.delete(key);
|
||||
}
|
||||
});
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
var y = d.getFullYear();
|
||||
var m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
var day = String(d.getDate()).padStart(2, '0');
|
||||
return y + '-' + m + '-' + day;
|
||||
}
|
||||
|
||||
// Dropdown toggle
|
||||
function openDropdown(popover) {
|
||||
popover.style.display = '';
|
||||
popover.closest('.filter-dd').classList.add('is-open');
|
||||
}
|
||||
|
||||
function closeDropdown(popover) {
|
||||
popover.style.display = 'none';
|
||||
popover.closest('.filter-dd').classList.remove('is-open');
|
||||
}
|
||||
|
||||
function toggleDropdown(popover) {
|
||||
if (popover.style.display === 'none') {
|
||||
openDropdown(popover);
|
||||
} else {
|
||||
closeDropdown(popover);
|
||||
}
|
||||
}
|
||||
|
||||
// Close dropdowns on outside click
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!e.target.closest('#filter-date-dd')) {
|
||||
closeDropdown(datePopover);
|
||||
}
|
||||
if (axePopover && !e.target.closest('#filter-axe-dd')) {
|
||||
closeDropdown(axePopover);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Date dropdown ---
|
||||
dateBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (axePopover) closeDropdown(axePopover);
|
||||
toggleDropdown(datePopover);
|
||||
});
|
||||
|
||||
// Date presets
|
||||
datePopover.addEventListener('click', function (e) {
|
||||
var preset = e.target.dataset.preset;
|
||||
if (!preset) return;
|
||||
|
||||
var now = new Date();
|
||||
var from, to;
|
||||
|
||||
if (preset === 'week') {
|
||||
var day = now.getDay();
|
||||
var diff = day === 0 ? 6 : day - 1;
|
||||
from = new Date(now);
|
||||
from.setDate(now.getDate() - diff);
|
||||
to = new Date(from);
|
||||
to.setDate(from.getDate() + 6);
|
||||
} else if (preset === 'month') {
|
||||
from = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
} else if (preset === 'lastmonth') {
|
||||
from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
to = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
} else if (preset === 'upcoming') {
|
||||
from = now;
|
||||
to = null;
|
||||
}
|
||||
|
||||
window.location.href = buildUrl({
|
||||
date_from: formatDate(from),
|
||||
date_to: to ? formatDate(to) : ''
|
||||
});
|
||||
});
|
||||
|
||||
// Date apply → navigate
|
||||
dateApply.addEventListener('click', function () {
|
||||
window.location.href = buildUrl({
|
||||
date_from: dateFrom.value,
|
||||
date_to: dateTo.value
|
||||
});
|
||||
});
|
||||
|
||||
// --- Axe dropdown ---
|
||||
if (axeBtn && axePopover) {
|
||||
axeBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
closeDropdown(datePopover);
|
||||
toggleDropdown(axePopover);
|
||||
});
|
||||
|
||||
axePopover.addEventListener('click', function (e) {
|
||||
var li = e.target.closest('[data-axe-id]');
|
||||
if (!li) return;
|
||||
if (li.dataset.axeHref) {
|
||||
window.location.href = li.dataset.axeHref;
|
||||
} else {
|
||||
window.location.href = buildUrl({ axe: li.dataset.axeId });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
22
js/coloredWordsHero.js
Normal file
22
js/coloredWordsHero.js
Normal file
@@ -0,0 +1,22 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (document.querySelector('body').classList.contains('home')) {
|
||||
const colors = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
|
||||
const timeouts = new Map();
|
||||
|
||||
// Color changing on hover
|
||||
document.querySelectorAll('.color-changer').forEach(element => {
|
||||
element.addEventListener('mouseenter', (e) => {
|
||||
if (timeouts.has(e.target)) {
|
||||
clearTimeout(timeouts.get(e.target));
|
||||
}
|
||||
|
||||
e.target.style.color = colors[Math.floor(Math.random() * colors.length)];
|
||||
const timeoutId = setTimeout(() => {
|
||||
e.target.style.color = 'black';
|
||||
timeouts.delete(e.target);
|
||||
}, 2000);
|
||||
timeouts.set(e.target, timeoutId);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
43
js/fitPostCardTitle.js
Normal file
43
js/fitPostCardTitle.js
Normal file
@@ -0,0 +1,43 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function fitTitles() {
|
||||
var cards = document.querySelectorAll('.post-card');
|
||||
cards.forEach(function (card) {
|
||||
var h2 = card.querySelector('.gradient-container h2');
|
||||
if (!h2) return;
|
||||
|
||||
var container = h2.closest('.gradient-container');
|
||||
var maxHeight = container.clientHeight;
|
||||
var fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
|
||||
var minSize = 12;
|
||||
|
||||
h2.style.fontSize = '';
|
||||
fontSize = parseFloat(window.getComputedStyle(h2).fontSize);
|
||||
|
||||
while (h2.scrollHeight > maxHeight && fontSize > minSize) {
|
||||
fontSize -= 1;
|
||||
h2.style.fontSize = fontSize + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.fitPostCardTitles = fitTitles;
|
||||
|
||||
fitTitles();
|
||||
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(fitTitles, 200);
|
||||
});
|
||||
|
||||
// Re-fit after infinite scroll loads new cards
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
if (m.addedNodes.length) fitTitles();
|
||||
});
|
||||
});
|
||||
var grid = document.getElementById('post-grid');
|
||||
if (grid) {
|
||||
observer.observe(grid, { childList: true });
|
||||
}
|
||||
});
|
||||
37
js/frenchTypography.js
Normal file
37
js/frenchTypography.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* French typography: replaces normal spaces with non-breaking spaces
|
||||
* where French typographic rules require them.
|
||||
*/
|
||||
function applyFrenchTypography(elements) {
|
||||
elements.forEach(function (el) {
|
||||
var html = el.innerHTML;
|
||||
// Collapse multiple spaces into one (outside HTML tags)
|
||||
html = html.replace(/([^<>]) {2,}(?=[^<>])/g, '$1 ');
|
||||
// Space before ? ! : ; »
|
||||
html = html.replace(/ ([?!:;»])/g, '\u00A0$1');
|
||||
// Space after «
|
||||
html = html.replace(/(«) /g, '$1\u00A0');
|
||||
// "p. 42" → non-breaking space after "p."
|
||||
html = html.replace(/\bp\. /g, 'p.\u00A0');
|
||||
// "n° 3" → non-breaking space after "n°"
|
||||
html = html.replace(/\bn° /g, 'n°\u00A0');
|
||||
el.innerHTML = html;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
applyFrenchTypography(document.querySelectorAll('.post-card h2, .post-card .post-card__title'));
|
||||
|
||||
// Re-apply on any post-card dynamically added anywhere on the page
|
||||
new MutationObserver(function (mutations) {
|
||||
mutations.forEach(function (m) {
|
||||
m.addedNodes.forEach(function (node) {
|
||||
if (node.nodeType !== 1) return;
|
||||
var targets = node.classList && node.classList.contains('post-card')
|
||||
? node.querySelectorAll('h2, .post-card__title')
|
||||
: node.querySelectorAll('.post-card h2, .post-card .post-card__title');
|
||||
applyFrenchTypography(targets);
|
||||
});
|
||||
});
|
||||
}).observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
16
js/imageSwiper.js
Normal file
16
js/imageSwiper.js
Normal file
@@ -0,0 +1,16 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-image-swiper]').forEach(function (container) {
|
||||
new Swiper(container.querySelector('.swiper'), {
|
||||
slidesPerView: 1,
|
||||
loop: true,
|
||||
navigation: {
|
||||
nextEl: container.querySelector('.swiper-button-next'),
|
||||
prevEl: container.querySelector('.swiper-button-prev'),
|
||||
},
|
||||
pagination: {
|
||||
el: container.querySelector('.swiper-pagination'),
|
||||
clickable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
71
js/infiniteScroll.js
Normal file
71
js/infiniteScroll.js
Normal file
@@ -0,0 +1,71 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var sentinel = document.getElementById('scroll-sentinel');
|
||||
var grid = document.getElementById('post-grid');
|
||||
var spinner = document.getElementById('scroll-spinner');
|
||||
|
||||
if (!sentinel || !grid) return;
|
||||
|
||||
var page = 1;
|
||||
var loading = false;
|
||||
var done = false;
|
||||
|
||||
// Read filter params from sentinel data attributes (set server-side)
|
||||
var categoryId = sentinel.dataset.category || '';
|
||||
var axe = sentinel.dataset.axe || '';
|
||||
var dateFrom = sentinel.dataset.dateFrom || '';
|
||||
var dateTo = sentinel.dataset.dateTo || '';
|
||||
var taxonomy = sentinel.dataset.taxonomy || '';
|
||||
var termId = sentinel.dataset.term || '';
|
||||
var catFilter = sentinel.dataset.filterCat || '';
|
||||
var filterAutres = sentinel.dataset.filterAutres || '';
|
||||
var excludeCats = sentinel.dataset.excludeCats || '';
|
||||
var searchQuery = sentinel.dataset.search || '';
|
||||
|
||||
function fetchPosts() {
|
||||
if (loading || done) return;
|
||||
loading = true;
|
||||
page++;
|
||||
spinner.style.display = 'flex';
|
||||
|
||||
var data = new FormData();
|
||||
data.append('action', 'load_more_posts');
|
||||
data.append('page', page);
|
||||
data.append('nonce', infiniteScrollData.nonce);
|
||||
data.append('lang', infiniteScrollData.lang || 'fr');
|
||||
if (categoryId) data.append('category', categoryId);
|
||||
if (axe) data.append('axe', axe);
|
||||
if (dateFrom) data.append('date_from', dateFrom);
|
||||
if (dateTo) data.append('date_to', dateTo);
|
||||
if (taxonomy) data.append('taxonomy', taxonomy);
|
||||
if (termId) data.append('term', termId);
|
||||
if (catFilter) data.append('filter_cat', catFilter);
|
||||
if (filterAutres) data.append('filter_autres', filterAutres);
|
||||
if (excludeCats) data.append('exclude_cats', excludeCats);
|
||||
if (searchQuery) data.append('search', searchQuery);
|
||||
|
||||
fetch(infiniteScrollData.ajaxUrl, { method: 'POST', body: data })
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (result) {
|
||||
spinner.style.display = 'none';
|
||||
if (result.success && result.data.html) {
|
||||
grid.insertAdjacentHTML('beforeend', result.data.html);
|
||||
loading = false;
|
||||
} else {
|
||||
done = true;
|
||||
observer.disconnect();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
spinner.style.display = 'none';
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
if (entries[0].isIntersecting) {
|
||||
fetchPosts();
|
||||
}
|
||||
}, { rootMargin: '200px' });
|
||||
|
||||
observer.observe(sentinel);
|
||||
});
|
||||
249
js/keywordCloud.js
Normal file
249
js/keywordCloud.js
Normal file
@@ -0,0 +1,249 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var container = document.getElementById('keyword-container');
|
||||
var tags = window.thalimTags;
|
||||
|
||||
if (!container || !tags || !tags.length) return;
|
||||
|
||||
// — init —
|
||||
|
||||
var GAP = 14;
|
||||
var COLORS = ['#e0775d', '#7cc0c6', '#e05680', '#46ae51', '#bb8dd9'];
|
||||
var colorTimeouts = new Map();
|
||||
|
||||
// Mélange aléatoire pour varier la disposition à chaque chargement
|
||||
var shuffled = tags.slice().sort(function () { return Math.random() - 0.5; });
|
||||
|
||||
// Crée les éléments une seule fois (réutilisés à chaque layout)
|
||||
var items = shuffled.map(function (tag) {
|
||||
var el = document.createElement('a');
|
||||
el.className = 'keyword';
|
||||
el.href = tag.url;
|
||||
el.textContent = tag.name;
|
||||
|
||||
el.addEventListener('mouseenter', function () {
|
||||
if (colorTimeouts.has(el)) clearTimeout(colorTimeouts.get(el));
|
||||
el.style.color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
colorTimeouts.set(el, setTimeout(function () {
|
||||
el.style.color = '';
|
||||
colorTimeouts.delete(el);
|
||||
}, 2000));
|
||||
});
|
||||
|
||||
container.appendChild(el);
|
||||
return { el: el, w: 0, h: 0 };
|
||||
});
|
||||
|
||||
var gradOverlay = null;
|
||||
var lastLayoutWidth = 0;
|
||||
|
||||
function layoutCloud() {
|
||||
var cw = container.offsetWidth;
|
||||
if (cw === lastLayoutWidth) return;
|
||||
lastLayoutWidth = cw;
|
||||
|
||||
var cx = cw / 2;
|
||||
var isMobile = cw < 768;
|
||||
var a = cx - GAP; // demi-grand axe horizontal
|
||||
var b = Math.round(cw * (isMobile ? 0.45 : 0.20)); // demi-petit axe vertical (plus haut sur mobile)
|
||||
var R_V = Math.round(b * (isMobile ? 0.45 : 0.70)); // demi-axe vertical de la zone d'exclusion (plus petit sur mobile)
|
||||
var R_H = Math.round(b * (isMobile ? 0.70 : 1.15)); // demi-axe horizontal (plus petit sur mobile)
|
||||
|
||||
// Re-mesurer les éléments (la taille peut changer avec le viewport)
|
||||
// +1 compense les arrondis sub-pixel sur écrans haute densité
|
||||
items.forEach(function (item) {
|
||||
var rect = item.el.getBoundingClientRect();
|
||||
item.w = Math.ceil(rect.width) + 1;
|
||||
item.h = Math.ceil(rect.height) + 1;
|
||||
});
|
||||
|
||||
var placed = [];
|
||||
|
||||
function hasOverlap(x, y, w, h) {
|
||||
for (var i = 0; i < placed.length; i++) {
|
||||
var p = placed[i];
|
||||
if (x < p.x + p.w + GAP &&
|
||||
x + w + GAP > p.x &&
|
||||
y < p.y + p.h + GAP &&
|
||||
y + h + GAP > p.y) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Placement contraint à l'anneau elliptique :
|
||||
// { intérieur ellipse (a, b) } ∩ { extérieur ellipse d'exclusion (R_H, R_V) }
|
||||
// Les candidats sont triés par distance à l'ellipse d'exclusion (croissante) :
|
||||
// les mots s'accumulent au plus près du centre avant de s'étendre.
|
||||
function findPos(w, h) {
|
||||
var candidates = [];
|
||||
|
||||
for (var x = 0; x <= cw - w; x += 8) {
|
||||
var dx = (x + w / 2) - cx;
|
||||
var ratio = dx / a;
|
||||
if (Math.abs(ratio) >= 1) continue;
|
||||
|
||||
// Limite verticale du centre du mot imposée par l'ellipse externe (avec marge h/2)
|
||||
var maxPcy = b * Math.sqrt(1 - ratio * ratio);
|
||||
if (maxPcy < h / 2) continue;
|
||||
var maxAbsY = maxPcy - h / 2;
|
||||
|
||||
// Limite verticale minimale imposée par l'ellipse d'exclusion (R_H, R_V)
|
||||
var minAbsY = (Math.abs(dx) >= R_H) ? 0
|
||||
: R_V * Math.sqrt(Math.max(0, 1 - (dx * dx) / (R_H * R_H)));
|
||||
if (minAbsY > maxAbsY) continue;
|
||||
|
||||
for (var absY = minAbsY; absY <= maxAbsY; absY += 4) {
|
||||
// Distance normalisée à l'ellipse d'exclusion (0 = sur le bord)
|
||||
var nx = dx / R_H, ny = absY / R_V;
|
||||
var dist = Math.sqrt(nx * nx + ny * ny) - 1;
|
||||
var yB = Math.round(absY - h / 2);
|
||||
if (absY > 0) {
|
||||
candidates.push({ x: x, y: Math.round(-absY - h / 2), dist: dist });
|
||||
}
|
||||
candidates.push({ x: x, y: yB, dist: dist });
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par distance à l'ellipse d'exclusion croissante → attraction vers le centre
|
||||
candidates.sort(function (ca, cb) { return ca.dist - cb.dist; });
|
||||
|
||||
for (var i = 0; i < candidates.length; i++) {
|
||||
var c = candidates[i];
|
||||
if (!hasOverlap(c.x, c.y, w, h)) return { x: c.x, y: c.y };
|
||||
}
|
||||
|
||||
return null; // aucune position dans l'anneau
|
||||
}
|
||||
|
||||
// Place les mots dans l'anneau elliptique, collecte les débordements
|
||||
var overflow = [];
|
||||
items.forEach(function (item) {
|
||||
var pos = findPos(item.w, item.h);
|
||||
if (pos) {
|
||||
item.pos = { x: pos.x, y: pos.y, w: item.w, h: item.h };
|
||||
placed.push(item.pos);
|
||||
item.el.style.left = pos.x + 'px';
|
||||
item.el.style.top = pos.y + 'px';
|
||||
} else {
|
||||
overflow.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Placement des débordements en lignes centrées (style flex-wrap center)
|
||||
if (overflow.length) {
|
||||
var startY = placed.reduce(function (m, p) { return Math.max(m, p.y + p.h); }, 0) + GAP;
|
||||
var rows = [];
|
||||
var currentRow = [];
|
||||
var currentRowW = 0;
|
||||
|
||||
overflow.forEach(function (item) {
|
||||
var needed = currentRowW > 0 ? item.w + GAP : item.w;
|
||||
if (currentRowW + needed > cw && currentRow.length > 0) {
|
||||
rows.push(currentRow);
|
||||
currentRow = [];
|
||||
currentRowW = 0;
|
||||
}
|
||||
currentRow.push(item);
|
||||
currentRowW += (currentRowW > 0 ? GAP : 0) + item.w;
|
||||
});
|
||||
if (currentRow.length) rows.push(currentRow);
|
||||
|
||||
var curY = startY;
|
||||
rows.forEach(function (row) {
|
||||
var rowW = row.reduce(function (s, item) { return s + item.w; }, 0) + (row.length - 1) * GAP;
|
||||
var rowH = row.reduce(function (m, item) { return Math.max(m, item.h); }, 0);
|
||||
var offsetX = Math.round((cw - rowW) / 2);
|
||||
|
||||
row.forEach(function (item) {
|
||||
item.pos = { x: offsetX, y: curY, w: item.w, h: item.h };
|
||||
placed.push(item.pos);
|
||||
item.el.style.left = offsetX + 'px';
|
||||
item.el.style.top = curY + 'px';
|
||||
offsetX += item.w + GAP;
|
||||
});
|
||||
curY += rowH + GAP;
|
||||
});
|
||||
}
|
||||
|
||||
// Normalisation Y : cy=0 → shift px depuis le haut du conteneur
|
||||
var minY = items.reduce(function (m, item) { return Math.min(m, item.pos.y); }, Infinity);
|
||||
var shift = Math.max(0, GAP - minY);
|
||||
|
||||
items.forEach(function (item) {
|
||||
item.pos.y += shift;
|
||||
item.el.style.top = item.pos.y + 'px';
|
||||
});
|
||||
|
||||
// Hauteur basée sur le contenu réel (évite le débordement sur mobile)
|
||||
var maxPlacedY = items.reduce(function (m, item) { return Math.max(m, item.pos.y + item.pos.h); }, 0);
|
||||
container.style.height = (maxPlacedY + GAP) + 'px';
|
||||
|
||||
// Dégradé sur un overlay séparé pour permettre l'animation scale au survol
|
||||
if (gradOverlay) {
|
||||
container.removeChild(gradOverlay);
|
||||
}
|
||||
gradOverlay = document.createElement('div');
|
||||
gradOverlay.style.cssText =
|
||||
'position:absolute;inset:0;pointer-events:none;' +
|
||||
'background:radial-gradient(circle ' + Math.round(R_V * 2) + 'px at ' + cx + 'px ' + shift + 'px,' +
|
||||
'rgba(247,255,41,1) 0%,rgba(247,255,41,0.6) 16%,rgba(247,255,41,0.15) 55%,transparent 70%);' +
|
||||
'transform-origin:' + cx + 'px ' + shift + 'px;' +
|
||||
'transition:transform 0.4s ease;';
|
||||
container.insertBefore(gradOverlay, container.firstChild);
|
||||
}
|
||||
|
||||
// Premier layout
|
||||
layoutCloud();
|
||||
|
||||
// Événements du dégradé (référencent gradOverlay via closure)
|
||||
container.addEventListener('mouseenter', function () {
|
||||
if (!gradOverlay) return;
|
||||
gradOverlay.style.transition = 'transform 0.4s ease';
|
||||
gradOverlay.style.transform = 'scale(0.82)';
|
||||
});
|
||||
container.addEventListener('mousemove', function (e) {
|
||||
if (!gradOverlay) return;
|
||||
var cw = container.offsetWidth;
|
||||
var cx = cw / 2;
|
||||
var rect = container.getBoundingClientRect();
|
||||
var dx = (e.clientX - rect.left) - cx;
|
||||
var dy = (e.clientY - rect.top) - parseFloat(gradOverlay.style.transformOrigin.split(' ')[1]);
|
||||
var tx = (-dx * 0.09).toFixed(2);
|
||||
var ty = (-dy * 0.09).toFixed(2);
|
||||
gradOverlay.style.transition = 'transform 0.15s ease-out';
|
||||
gradOverlay.style.transform = 'translate(' + tx + 'px,' + ty + 'px) scale(0.82)';
|
||||
});
|
||||
container.addEventListener('mouseleave', function () {
|
||||
if (!gradOverlay) return;
|
||||
gradOverlay.style.transition = 'transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94)';
|
||||
gradOverlay.style.transform = 'scale(1)';
|
||||
});
|
||||
|
||||
// Resize avec debounce
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function () {
|
||||
lastLayoutWidth = 0; // forcer le recalcul
|
||||
layoutCloud();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
// Animation d'apparition au scroll
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
items.forEach(function (item, i) {
|
||||
item.el.style.animationDelay = (i * 0.03) + 's';
|
||||
item.el.classList.add('keyword--visible');
|
||||
});
|
||||
observer.unobserve(container);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.05 });
|
||||
|
||||
observer.observe(container);
|
||||
});
|
||||
})();
|
||||
229
js/membresFilters.js
Normal file
229
js/membresFilters.js
Normal file
@@ -0,0 +1,229 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// ── Filters toggle ────────────────────────────────────────
|
||||
var membresToggleBtn = document.getElementById('membres-filters-toggle');
|
||||
var membresFiltersEl = document.getElementById('membres-filters');
|
||||
|
||||
if (membresToggleBtn && membresFiltersEl) {
|
||||
membresToggleBtn.addEventListener('click', function () {
|
||||
var isOpen = membresFiltersEl.classList.toggle('is-open');
|
||||
membresToggleBtn.classList.toggle('is-open', isOpen);
|
||||
membresToggleBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
if (isOpen) {
|
||||
var searchInput = document.getElementById('membres-search');
|
||||
if (searchInput) searchInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var activeRole = '';
|
||||
var searchQuery = '';
|
||||
|
||||
// ── Dropdown (role filter) ────────────────────────────────
|
||||
var roleBtn = document.getElementById('filter-role-btn');
|
||||
var rolePopover = document.getElementById('filter-role-popover');
|
||||
var roleLabel = document.getElementById('filter-role-label');
|
||||
var roleReset = document.getElementById('role-reset');
|
||||
var roleDd = document.getElementById('filter-role-dd');
|
||||
|
||||
function openRole() { rolePopover.style.display = ''; roleDd.classList.add('is-open'); }
|
||||
function closeRole() { rolePopover.style.display = 'none'; roleDd.classList.remove('is-open'); }
|
||||
|
||||
roleBtn.addEventListener('click', function () {
|
||||
rolePopover.style.display === 'none' ? openRole() : closeRole();
|
||||
});
|
||||
|
||||
rolePopover.querySelectorAll('[data-role]').forEach(function (item) {
|
||||
item.addEventListener('click', function () {
|
||||
activeRole = item.dataset.role;
|
||||
roleLabel.textContent = item.textContent.trim();
|
||||
roleReset.style.display = activeRole ? '' : 'none';
|
||||
roleDd.classList.toggle('is-active', !!activeRole);
|
||||
closeRole();
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
});
|
||||
|
||||
roleReset.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
activeRole = '';
|
||||
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
|
||||
roleReset.style.display = 'none';
|
||||
roleDd.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!roleDd.contains(e.target)) closeRole();
|
||||
});
|
||||
|
||||
// ── Search input ──────────────────────────────────────────
|
||||
var searchInput = document.getElementById('membres-search');
|
||||
var searchReset = document.getElementById('search-reset');
|
||||
|
||||
searchInput.addEventListener('input', function () {
|
||||
searchQuery = searchInput.value.trim().toLowerCase();
|
||||
searchReset.style.display = searchQuery ? '' : 'none';
|
||||
searchInput.classList.toggle('is-active', !!searchQuery);
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
searchReset.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
searchQuery = '';
|
||||
searchInput.value = '';
|
||||
searchReset.style.display = 'none';
|
||||
searchInput.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// ── Active chips ──────────────────────────────────────────
|
||||
var chipsContainer = document.getElementById('membres-active-chips');
|
||||
|
||||
function makeChip(label, onRemove) {
|
||||
var btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'filter-chip';
|
||||
btn.innerHTML = label + '<i class="iconoir-xmark"></i>';
|
||||
btn.addEventListener('click', onRemove);
|
||||
return btn;
|
||||
}
|
||||
|
||||
function updateChips() {
|
||||
if (!chipsContainer) return;
|
||||
chipsContainer.innerHTML = '';
|
||||
if (activeRole) {
|
||||
chipsContainer.appendChild(makeChip(activeRole, function () {
|
||||
activeRole = '';
|
||||
roleLabel.textContent = rolePopover.querySelector('[data-role=""]').textContent.trim();
|
||||
roleReset.style.display = 'none';
|
||||
roleDd.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
}));
|
||||
}
|
||||
if (searchQuery) {
|
||||
chipsContainer.appendChild(makeChip(searchInput.value, function () {
|
||||
searchQuery = '';
|
||||
searchInput.value = '';
|
||||
searchReset.style.display = 'none';
|
||||
searchInput.classList.remove('is-active');
|
||||
updateChips();
|
||||
applyFilters();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Column sort ───────────────────────────────────────────
|
||||
var sortKey = 'nom';
|
||||
var sortDir = 'asc';
|
||||
|
||||
function getSortValue(row, key) {
|
||||
if (key === 'nom') {
|
||||
return (row.dataset.sortName || '').trim().toLowerCase();
|
||||
}
|
||||
if (key === 'statut') return (row.dataset.status || '').toLowerCase();
|
||||
if (key === 'affiliation') return (row.dataset.affiliation || '').toLowerCase();
|
||||
return '';
|
||||
}
|
||||
|
||||
function applySortToTable(tbody) {
|
||||
var rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
rows.sort(function (a, b) {
|
||||
var va = getSortValue(a, sortKey);
|
||||
var vb = getSortValue(b, sortKey);
|
||||
var cmp = va.localeCompare(vb, 'fr', { sensitivity: 'base' });
|
||||
return sortDir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
rows.forEach(function (row) { tbody.appendChild(row); });
|
||||
}
|
||||
|
||||
function applySort() {
|
||||
document.querySelectorAll('.membres-table tbody').forEach(function (tbody) {
|
||||
if (!tbody.closest('table').hasAttribute('data-fixed-order')) {
|
||||
applySortToTable(tbody);
|
||||
}
|
||||
restripe(tbody);
|
||||
});
|
||||
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
|
||||
th.classList.remove('sort-asc', 'sort-desc');
|
||||
if (th.dataset.sort === sortKey) {
|
||||
th.classList.add('sort-' + sortDir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.membres-table th[data-sort]').forEach(function (th) {
|
||||
th.addEventListener('click', function () {
|
||||
var key = th.dataset.sort;
|
||||
if (sortKey === key) {
|
||||
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
sortKey = key;
|
||||
sortDir = 'asc';
|
||||
}
|
||||
applySort();
|
||||
});
|
||||
});
|
||||
|
||||
applySort();
|
||||
|
||||
// ── Row restriping (fixes alternating colors after filter) ──
|
||||
function restripe(tbody) {
|
||||
var n = 0;
|
||||
tbody.querySelectorAll('tr').forEach(function (row) {
|
||||
if (row.style.display === 'none') return;
|
||||
row.classList.toggle('is-even-row', n % 2 === 1);
|
||||
n++;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Apply both filters ────────────────────────────────────
|
||||
function applyFilters() {
|
||||
var isFiltering = activeRole || searchQuery;
|
||||
|
||||
document.querySelectorAll('.membres-item').forEach(function (item) {
|
||||
// Filter rows by role and/or name
|
||||
var rows = item.querySelectorAll('tbody tr');
|
||||
var visible = 0;
|
||||
rows.forEach(function (row) {
|
||||
var name = (row.dataset.name || '').toLowerCase();
|
||||
var status = (row.dataset.status || '').toLowerCase();
|
||||
var affiliation = (row.dataset.affiliation || '').toLowerCase();
|
||||
var roles = (row.dataset.roles || '').split('|').map(function (r) { return r.trim().toLowerCase(); });
|
||||
|
||||
var matchesRole = !activeRole || roles.includes(activeRole.toLowerCase());
|
||||
var matchesName = !searchQuery || name.includes(searchQuery)
|
||||
|| status.includes(searchQuery)
|
||||
|| affiliation.includes(searchQuery);
|
||||
|
||||
var show = matchesRole && matchesName;
|
||||
row.style.display = show ? '' : 'none';
|
||||
if (show) visible++;
|
||||
});
|
||||
item.querySelectorAll('tbody').forEach(restripe);
|
||||
|
||||
// Hide group if no rows are visible
|
||||
if (isFiltering && visible === 0) {
|
||||
item.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
item.style.display = '';
|
||||
|
||||
var content = item.querySelector('.membres-content');
|
||||
if (isFiltering) {
|
||||
// Auto-expand when a filter is active
|
||||
content.style.display = '';
|
||||
item.classList.add('is-open');
|
||||
} else {
|
||||
// Collapse back when all filters are cleared
|
||||
content.style.display = 'none';
|
||||
item.classList.remove('is-open');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
81
js/membresPopover.js
Normal file
81
js/membresPopover.js
Normal file
@@ -0,0 +1,81 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var section = document.querySelector('.membres-section');
|
||||
if (!section) return;
|
||||
|
||||
// Build the popover element once and append to body
|
||||
var popover = document.createElement('div');
|
||||
popover.id = 'membre-popover';
|
||||
popover.innerHTML =
|
||||
'<div class="membre-popover-inner">' +
|
||||
'<img class="membre-popover-pic" src="" alt="">' +
|
||||
'<div class="membre-popover-info">' +
|
||||
'<p class="membre-popover-name"></p>' +
|
||||
'<p class="membre-popover-status"></p>' +
|
||||
'<p class="membre-popover-domaines"></p>' +
|
||||
'<p class="membre-popover-autres"></p>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(popover);
|
||||
|
||||
var pic = popover.querySelector('.membre-popover-pic');
|
||||
var elName = popover.querySelector('.membre-popover-name');
|
||||
var elStat = popover.querySelector('.membre-popover-status');
|
||||
var elDom = popover.querySelector('.membre-popover-domaines');
|
||||
var elAut = popover.querySelector('.membre-popover-autres');
|
||||
|
||||
var visible = false;
|
||||
var currentRow = null;
|
||||
|
||||
// ── Show/hide via event delegation on the section ────────
|
||||
section.addEventListener('mouseover', function (e) {
|
||||
var row = e.target.closest('tbody tr');
|
||||
if (!row || row === currentRow) return;
|
||||
currentRow = row;
|
||||
|
||||
var avatar = row.dataset.avatar || '';
|
||||
if (avatar) {
|
||||
pic.src = avatar;
|
||||
pic.style.display = '';
|
||||
} else {
|
||||
pic.src = '';
|
||||
pic.style.display = 'none';
|
||||
}
|
||||
|
||||
elName.textContent = row.dataset.name || '';
|
||||
elStat.textContent = row.dataset.status || '';
|
||||
|
||||
var domVal = row.dataset.domaines || '';
|
||||
elDom.textContent = domVal;
|
||||
elDom.style.display = domVal ? '' : 'none';
|
||||
|
||||
var autVal = row.dataset.autresDomaines || '';
|
||||
elAut.innerHTML = autVal.replace(/\n/g, '<br>');
|
||||
elAut.style.display = autVal ? '' : 'none';
|
||||
|
||||
popover.classList.add('is-visible');
|
||||
visible = true;
|
||||
});
|
||||
|
||||
section.addEventListener('mouseout', function (e) {
|
||||
var row = e.target.closest('tbody tr');
|
||||
if (!row) return;
|
||||
// only hide when leaving the row entirely (not moving to a child)
|
||||
if (e.relatedTarget && row.contains(e.relatedTarget)) return;
|
||||
currentRow = null;
|
||||
popover.classList.remove('is-visible');
|
||||
visible = false;
|
||||
});
|
||||
|
||||
// ── Follow the cursor ─────────────────────────────────────
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
if (!visible) return;
|
||||
var x = e.clientX + 18;
|
||||
var y = e.clientY + 18;
|
||||
var pw = popover.offsetWidth;
|
||||
var ph = popover.offsetHeight;
|
||||
if (x + pw > window.innerWidth) x = e.clientX - pw - 8;
|
||||
if (y + ph > window.innerHeight) y = e.clientY - ph - 8;
|
||||
popover.style.left = x + 'px';
|
||||
popover.style.top = y + 'px';
|
||||
});
|
||||
});
|
||||
57
js/messageLabo.js
Normal file
57
js/messageLabo.js
Normal file
@@ -0,0 +1,57 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var messageList = document.querySelector('.messages-list');
|
||||
var agendaContent = document.querySelector('.agenda-content');
|
||||
var sectionTitle = document.querySelector('.message-du-labo .section-title');
|
||||
var buttonMessages = document.querySelector('.button-messages');
|
||||
if (!messageList || !agendaContent) return;
|
||||
|
||||
var items = Array.from(messageList.querySelectorAll('.message-item'));
|
||||
|
||||
function sync() {
|
||||
items.forEach(function (item) {
|
||||
item.style.display = '';
|
||||
var content = item.querySelector('.message-content');
|
||||
if (content) { content.style.maxHeight = ''; content.classList.remove('is-overflowing'); }
|
||||
});
|
||||
|
||||
if (window.innerWidth < 768) {
|
||||
// Mobile : afficher uniquement le premier message
|
||||
items.forEach(function (item, i) { item.style.display = i === 0 ? '' : 'none'; });
|
||||
return;
|
||||
}
|
||||
|
||||
var budget = agendaContent.offsetHeight
|
||||
- (sectionTitle ? sectionTitle.offsetHeight : 0)
|
||||
- (buttonMessages ? buttonMessages.offsetHeight : 0);
|
||||
var used = 0;
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var item = items[i];
|
||||
var itemHeight = item.offsetHeight;
|
||||
if (used + itemHeight <= budget) {
|
||||
used += itemHeight;
|
||||
} else {
|
||||
var remaining = budget - used;
|
||||
var content = item.querySelector('.message-content');
|
||||
if (content && remaining > 100) {
|
||||
var contentBudget = remaining - (itemHeight - content.offsetHeight);
|
||||
if (contentBudget > 60) {
|
||||
content.style.maxHeight = contentBudget + 'px';
|
||||
content.classList.add('is-overflowing');
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
for (var j = i + 1; j < items.length; j++) {
|
||||
items[j].style.display = 'none';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sync();
|
||||
window.addEventListener('resize', sync);
|
||||
});
|
||||
32
js/navAxesToggle.js
Normal file
32
js/navAxesToggle.js
Normal file
@@ -0,0 +1,32 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var item = document.querySelector('.nav-axes-item');
|
||||
if (!item) return;
|
||||
|
||||
var trigger = item.querySelector('.nav-axes-trigger');
|
||||
var mainMenu = document.querySelector('.main-menu');
|
||||
|
||||
trigger.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = item.classList.toggle('is-open');
|
||||
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||
});
|
||||
|
||||
// Close when clicking outside
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!item.contains(e.target)) {
|
||||
item.classList.remove('is-open');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// Reset when main menu closes
|
||||
if (mainMenu) {
|
||||
var observer = new MutationObserver(function () {
|
||||
if (!mainMenu.classList.contains('active')) {
|
||||
item.classList.remove('is-open');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
observer.observe(mainMenu, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
});
|
||||
133
js/overlay.js
Normal file
133
js/overlay.js
Normal file
@@ -0,0 +1,133 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const header = document.querySelector('header');
|
||||
const body = document.querySelector('body');
|
||||
const menuToggle = document.querySelector('.menu-toggle');
|
||||
const mainMenu = document.querySelector('.main-menu');
|
||||
const menuOverlay = document.querySelector('.overlay');
|
||||
const menuIcon = document.querySelector('.menu-toggle i');
|
||||
const wpAdminBar = document.querySelector('#wpadminbar');
|
||||
const stickyHeaderMobile = document.querySelector('.header-right');
|
||||
const searchButton = document.querySelector('.search-button');
|
||||
const searchPanel = document.querySelector('.search-panel');
|
||||
const searchIcon = document.querySelector('.search-button i');
|
||||
const searchInput = document.querySelector('.search-panel__input');
|
||||
|
||||
const breakpointTablet = 768;
|
||||
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
|
||||
// Compute the pixel offset at which panels should appear (just below the header)
|
||||
function getHeaderBottom() {
|
||||
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
return stickyHeaderMobile.getBoundingClientRect().bottom + window.scrollY;
|
||||
}
|
||||
return header.getBoundingClientRect().bottom + window.scrollY;
|
||||
}
|
||||
return header.offsetHeight + adminBarHeight;
|
||||
}
|
||||
|
||||
function updateOverlay() {
|
||||
const anyOpen = mainMenu.classList.contains('active') || searchPanel.classList.contains('active');
|
||||
menuOverlay.classList.toggle('active', anyOpen);
|
||||
if (anyOpen) {
|
||||
body.style.overflow = 'hidden';
|
||||
} else {
|
||||
body.style.removeProperty('overflow');
|
||||
}
|
||||
}
|
||||
|
||||
// --- Menu ---
|
||||
function openMenu() {
|
||||
mainMenu.scrollTo(0, 0);
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
const adminBarHeight = wpAdminBar ? wpAdminBar.offsetHeight : 0;
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
mainMenu.style.height = `${window.innerHeight - adminBarHeight - stickyHeaderMobile.offsetHeight}px`;
|
||||
} else {
|
||||
mainMenu.style.height = `${window.innerHeight - header.getBoundingClientRect().bottom}px`;
|
||||
}
|
||||
} else {
|
||||
mainMenu.style.removeProperty('height');
|
||||
}
|
||||
mainMenu.style.top = `${getHeaderBottom()}px`;
|
||||
mainMenu.classList.add('active');
|
||||
menuIcon.classList.remove('iconoir-menu');
|
||||
menuIcon.classList.add('iconoir-xmark');
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
mainMenu.classList.remove('active');
|
||||
menuIcon.classList.remove('iconoir-xmark');
|
||||
menuIcon.classList.add('iconoir-menu');
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
if (mainMenu.classList.contains('active')) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
// --- Search ---
|
||||
function openSearch() {
|
||||
searchPanel.style.top = `${getHeaderBottom()}px`;
|
||||
searchPanel.classList.add('active');
|
||||
searchIcon.classList.remove('iconoir-search');
|
||||
searchIcon.classList.add('iconoir-xmark');
|
||||
setTimeout(function() { if (searchInput) searchInput.focus(); }, 400);
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
searchPanel.classList.remove('active');
|
||||
searchIcon.classList.remove('iconoir-xmark');
|
||||
searchIcon.classList.add('iconoir-search');
|
||||
}
|
||||
|
||||
function toggleSearch() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) {
|
||||
closeSearch();
|
||||
} else {
|
||||
openSearch();
|
||||
}
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
// --- Event listeners ---
|
||||
menuToggle.addEventListener('click', toggleMenu);
|
||||
searchButton.addEventListener('click', toggleSearch);
|
||||
|
||||
menuOverlay.addEventListener('click', function() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
if (mainMenu.classList.contains('active')) closeMenu();
|
||||
if (searchPanel.classList.contains('active')) closeSearch();
|
||||
updateOverlay();
|
||||
mainMenu.style.top = `${mainMenu.offsetHeight * -1}px`;
|
||||
searchPanel.style.top = `${searchPanel.offsetHeight * -1}px`;
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
24
js/quickLinks.js
Normal file
24
js/quickLinks.js
Normal file
@@ -0,0 +1,24 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var quickLinks = document.querySelector('.quick-links');
|
||||
if (!quickLinks) return;
|
||||
|
||||
// Last section: keyword cloud if present, otherwise last swiper section
|
||||
var lastSection = document.querySelector('.keyword-cloud');
|
||||
if (!lastSection) {
|
||||
var swiperSections = document.querySelectorAll('.swiper-section');
|
||||
lastSection = swiperSections[swiperSections.length - 1] || null;
|
||||
}
|
||||
if (!lastSection) return;
|
||||
|
||||
var initialTop = quickLinks.getBoundingClientRect().top + window.scrollY;
|
||||
var quickLinksHeight = quickLinks.offsetHeight;
|
||||
|
||||
window.addEventListener('scroll', function () {
|
||||
var sectionBottom = lastSection.getBoundingClientRect().bottom;
|
||||
if (initialTop - sectionBottom > 0) {
|
||||
quickLinks.style.top = (initialTop - (initialTop - sectionBottom) - quickLinksHeight) + 'px';
|
||||
} else {
|
||||
quickLinks.style.top = initialTop + 'px';
|
||||
}
|
||||
});
|
||||
});
|
||||
39
js/seanceToggle.js
Normal file
39
js/seanceToggle.js
Normal file
@@ -0,0 +1,39 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('[data-seance-toggle]').forEach(function (header) {
|
||||
header.addEventListener('click', function (e) {
|
||||
// Don't toggle when clicking a link
|
||||
if (e.target.closest('a')) return;
|
||||
|
||||
var item = header.closest('.seance-item, .membres-item, .author-posts-item, .labo-dropdown-item');
|
||||
var content = item.querySelector('.seance-content, .membres-content, .author-posts-content, .labo-dropdown-content');
|
||||
var isOpen = item.classList.contains('is-open');
|
||||
|
||||
if (isOpen) {
|
||||
content.style.display = 'none';
|
||||
item.classList.remove('is-open');
|
||||
} else {
|
||||
content.style.display = '';
|
||||
item.classList.add('is-open');
|
||||
if (window.fitPostCardTitles) window.fitPostCardTitles();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-expand and scroll to a séance targeted by URL hash (#seance-{ID})
|
||||
var hash = window.location.hash;
|
||||
if (hash && hash.startsWith('#seance-')) {
|
||||
var target = document.querySelector(hash + '.seance-item');
|
||||
if (target) {
|
||||
var content = target.querySelector('.seance-content');
|
||||
if (content) {
|
||||
content.style.display = '';
|
||||
target.classList.add('is-open');
|
||||
if (window.fitPostCardTitles) window.fitPostCardTitles();
|
||||
setTimeout(function () {
|
||||
var top = target.getBoundingClientRect().top + window.scrollY - 150;
|
||||
window.scrollTo({ top: top, behavior: 'smooth' });
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
91
js/stickyHeader.js
Normal file
91
js/stickyHeader.js
Normal file
@@ -0,0 +1,91 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const header = document.querySelector('header');
|
||||
const wpAdminBar = document.querySelector('#wpadminbar');
|
||||
|
||||
const stickyHeaderMobile = document.querySelector('.header-right');
|
||||
const relativeHeaderMobile = document.querySelector('.header-left');
|
||||
|
||||
const mainLogo = document.querySelector('.main-logo');
|
||||
const description = document.querySelector('.description');
|
||||
const burgerContainer = document.querySelector('.menu-toggle > div');
|
||||
const menuIconContainer = document.querySelector('.menu-toggle > div > div');
|
||||
const menuText = document.querySelector('.menu-toggle > div > p');
|
||||
|
||||
const breakpointTablet = 768;
|
||||
|
||||
function checkMobile() {
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
stickyHeaderMobile.style.top = wpAdminBar ? `${wpAdminBar.offsetHeight}px` : '0px';
|
||||
} else {
|
||||
stickyHeaderMobile.style.top = 'unset';
|
||||
}
|
||||
}
|
||||
|
||||
function resetStyles() {
|
||||
header.style.removeProperty("height");
|
||||
mainLogo.style.removeProperty("padding");
|
||||
description.style.removeProperty("opacity");
|
||||
burgerContainer.style.removeProperty("padding");
|
||||
burgerContainer.style.removeProperty("justify-content");
|
||||
menuIconContainer.style.removeProperty("font-size");
|
||||
menuText.style.removeProperty("display");
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const isScrolledTop = window.scrollY === 0;
|
||||
if (window.innerWidth < breakpointTablet) {
|
||||
// mobile
|
||||
if (window.scrollY > header.getBoundingClientRect().bottom) {
|
||||
// déployer petit logo à gauche
|
||||
if(!stickyHeaderMobile.classList.contains('scrolled')) {
|
||||
stickyHeaderMobile.classList.add('scrolled')
|
||||
}
|
||||
} else {
|
||||
// rétracter petit logo à gauche
|
||||
if(stickyHeaderMobile.classList.contains('scrolled')) {
|
||||
stickyHeaderMobile.classList.remove('scrolled');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// desktop
|
||||
header.style.height = isScrolledTop ? '12vh' : '6vh';
|
||||
header.style.minHeight = isScrolledTop ? '100px' : 'unset';
|
||||
mainLogo.style.padding = isScrolledTop ? '1.5rem 2rem' : '0.2rem 0.4rem';
|
||||
description.style.opacity = isScrolledTop ? '1' : '0';
|
||||
burgerContainer.style.padding = isScrolledTop ? '2rem' : '0.6rem 1rem';
|
||||
burgerContainer.style.justifyContent = isScrolledTop ? 'space-between' : 'center';
|
||||
menuIconContainer.style.fontSize = isScrolledTop ? '2rem' : '1.5rem';
|
||||
menuText.style.display = isScrolledTop ? '' : 'none';
|
||||
|
||||
|
||||
|
||||
if (window.scrollY === 0) {
|
||||
// agrandir le header
|
||||
} else {
|
||||
// diminuer le header
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
let resizeTimeout;
|
||||
let previousWidth = window.innerWidth;
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
let currentWidth = window.innerWidth;
|
||||
|
||||
if (currentWidth !== previousWidth) {
|
||||
window.scrollTo(0, 0);
|
||||
previousWidth = currentWidth;
|
||||
}
|
||||
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
resetStyles();
|
||||
checkMobile();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
checkMobile();
|
||||
});
|
||||
Reference in New Issue
Block a user